{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "ef0a4ee4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'C:\\\\Users\\\\IDEA'"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%pwd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "japanese-margin",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'C:\\\\Users\\\\IDEA'"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import os\n",
    "os.getcwd()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "executive-insulin",
   "metadata": {},
   "outputs": [],
   "source": [
    "os.chdir('E:\\\\experimentos\\\\2025\\\\Dataset_2')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "0888b480",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'E:\\\\experimentos\\\\2025\\\\Dataset_2'"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%pwd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "d29220ca",
   "metadata": {},
   "outputs": [],
   "source": [
    "from tensorflow import keras\n",
    "#keras.__version__\n",
    "import os, shutil"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "d979c4e9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "E:\\experimentos\\2025\\Dataset_2\n",
      "E:\\experimentos\\2025\\Dataset_2\\conv\n",
      "True\n"
     ]
    }
   ],
   "source": [
    "original_dir = os.path.abspath('E:\\\\experimentos\\\\2025\\\\Dataset_2')\n",
    "# The directory where we will\n",
    "# store our smaller dataset\n",
    "base_dir = os.path.join(original_dir, \"conv\")\n",
    "print(original_dir)\n",
    "print(base_dir)\n",
    "# added by reviewer\n",
    "isdir = os.path.isdir(base_dir) \n",
    "print(isdir) \n",
    "\n",
    "if isdir ==True:\n",
    "    shutil.rmtree(base_dir)\n",
    "\n",
    "os.mkdir(base_dir)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "6b486b83",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Directories for our training,\n",
    "# validation and test splits\n",
    "train_dir = os.path.join(base_dir, 'train')\n",
    "os.mkdir(train_dir)\n",
    "validation_dir = os.path.join(base_dir, 'validation')\n",
    "os.mkdir(validation_dir)\n",
    "\n",
    "\n",
    "\n",
    "# Directory with our training tm pictures\n",
    "train_tm_dir = os.path.join(train_dir, 'LS')\n",
    "os.mkdir(train_tm_dir)\n",
    "\n",
    "# Directory with our training cm pictures\n",
    "train_cm_dir = os.path.join(train_dir, 'SF')\n",
    "os.mkdir(train_cm_dir)\n",
    "\n",
    "# Directory with our training tramp pictures\n",
    "train_tramp_dir = os.path.join(train_dir, 'tmp')\n",
    "os.mkdir(train_tramp_dir)\n",
    "\n",
    "\n",
    "# Directory with our validation tm pictures\n",
    "validation_tm_dir = os.path.join(validation_dir, 'LS')\n",
    "os.mkdir(validation_tm_dir)\n",
    "\n",
    "# Directory with our validation cm pictures\n",
    "validation_cm_dir = os.path.join(validation_dir, 'SF')\n",
    "os.mkdir(validation_cm_dir)\n",
    "\n",
    "# Directory with our validation tramp pictures\n",
    "validation_tramp_dir = os.path.join(validation_dir, 'tmp')\n",
    "os.mkdir(validation_tramp_dir)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "d991503b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "75\n",
      "['LS.21.bmp', 'LS.44.bmp', 'LS.64.bmp', 'LS.39.bmp', 'LS.16.bmp', 'LS.50.bmp', 'LS.67.bmp', 'LS.70.bmp', 'LS.97.bmp', 'LS.99.bmp', 'LS.88.bmp', 'LS.43.bmp', 'LS.79.bmp', 'LS.55.bmp', 'LS.52.bmp', 'LS.7.bmp', 'LS.98.bmp', 'LS.89.bmp', 'LS.13.bmp', 'LS.29.bmp', 'LS.6.bmp', 'LS.75.bmp', 'LS.71.bmp', 'LS.40.bmp', 'LS.25.bmp', 'LS.76.bmp', 'LS.3.bmp', 'LS.57.bmp', 'LS.51.bmp', 'LS.11.bmp', 'LS.18.bmp', 'LS.24.bmp', 'LS.86.bmp', 'LS.65.bmp', 'LS.47.bmp', 'LS.95.bmp', 'LS.85.bmp', 'LS.72.bmp', 'LS.2.bmp', 'LS.100.bmp', 'LS.96.bmp', 'LS.14.bmp', 'LS.10.bmp', 'LS.32.bmp', 'LS.80.bmp', 'LS.37.bmp', 'LS.87.bmp', 'LS.90.bmp', 'LS.63.bmp', 'LS.27.bmp', 'LS.28.bmp', 'LS.93.bmp', 'LS.42.bmp', 'LS.91.bmp', 'LS.15.bmp', 'LS.49.bmp', 'LS.23.bmp', 'LS.34.bmp', 'LS.19.bmp', 'LS.69.bmp', 'LS.60.bmp', 'LS.12.bmp', 'LS.8.bmp', 'LS.68.bmp', 'LS.0.bmp', 'LS.92.bmp', 'LS.78.bmp', 'LS.56.bmp', 'LS.30.bmp', 'LS.74.bmp', 'LS.81.bmp', 'LS.84.bmp', 'LS.38.bmp', 'LS.22.bmp', 'LS.58.bmp']\n",
      "28\n",
      "['LS.33.bmp', 'LS.36.bmp', 'LS.82.bmp', 'LS.48.bmp', 'LS.45.bmp', 'LS.77.bmp', 'LS.17.bmp', 'LS.53.bmp', 'LS.5.bmp', 'LS.46.bmp', 'LS.31.bmp', 'LS.9.bmp', 'LS.41.bmp', 'LS.94.bmp', 'LS.66.bmp', 'LS.101.bmp', 'LS.20.bmp', 'LS.83.bmp', 'LS.35.bmp', 'LS.62.bmp', 'LS.59.bmp', 'LS.26.bmp', 'LS.1.bmp', 'LS.102.bmp', 'LS.61.bmp', 'LS.54.bmp', 'LS.4.bmp', 'LS.73.bmp']\n",
      "342\n",
      "['SF.180.bmp', 'SF.287.bmp', 'SF.289.bmp', 'SF.398.bmp', 'SF.303.bmp', 'SF.330.bmp', 'SF.29.bmp', 'SF.63.bmp', 'SF.342.bmp', 'SF.486.bmp', 'SF.8.bmp', 'SF.411.bmp', 'SF.257.bmp', 'SF.260.bmp', 'SF.375.bmp', 'SF.161.bmp', 'SF.109.bmp', 'SF.467.bmp', 'SF.96.bmp', 'SF.384.bmp', 'SF.64.bmp', 'SF.482.bmp', 'SF.441.bmp', 'SF.131.bmp', 'SF.128.bmp', 'SF.11.bmp', 'SF.52.bmp', 'SF.392.bmp', 'SF.190.bmp', 'SF.26.bmp', 'SF.58.bmp', 'SF.27.bmp', 'SF.201.bmp', 'SF.366.bmp', 'SF.408.bmp', 'SF.405.bmp', 'SF.324.bmp', 'SF.321.bmp', 'SF.35.bmp', 'SF.435.bmp', 'SF.66.bmp', 'SF.387.bmp', 'SF.417.bmp', 'SF.262.bmp', 'SF.323.bmp', 'SF.231.bmp', 'SF.320.bmp', 'SF.407.bmp', 'SF.326.bmp', 'SF.182.bmp', 'SF.111.bmp', 'SF.202.bmp', 'SF.343.bmp', 'SF.104.bmp', 'SF.313.bmp', 'SF.269.bmp', 'SF.141.bmp', 'SF.356.bmp', 'SF.10.bmp', 'SF.188.bmp', 'SF.0.bmp', 'SF.478.bmp', 'SF.34.bmp', 'SF.31.bmp', 'SF.60.bmp', 'SF.355.bmp', 'SF.79.bmp', 'SF.386.bmp', 'SF.206.bmp', 'SF.484.bmp', 'SF.460.bmp', 'SF.172.bmp', 'SF.385.bmp', 'SF.235.bmp', 'SF.402.bmp', 'SF.94.bmp', 'SF.246.bmp', 'SF.360.bmp', 'SF.53.bmp', 'SF.249.bmp', 'SF.170.bmp', 'SF.451.bmp', 'SF.171.bmp', 'SF.149.bmp', 'SF.261.bmp', 'SF.362.bmp', 'SF.340.bmp', 'SF.20.bmp', 'SF.62.bmp', 'SF.174.bmp', 'SF.358.bmp', 'SF.159.bmp', 'SF.263.bmp', 'SF.399.bmp', 'SF.327.bmp', 'SF.409.bmp', 'SF.469.bmp', 'SF.369.bmp', 'SF.135.bmp', 'SF.285.bmp', 'SF.118.bmp', 'SF.456.bmp', 'SF.158.bmp', 'SF.74.bmp', 'SF.306.bmp', 'SF.28.bmp', 'SF.39.bmp', 'SF.258.bmp', 'SF.335.bmp', 'SF.317.bmp', 'SF.6.bmp', 'SF.403.bmp', 'SF.312.bmp', 'SF.46.bmp', 'SF.143.bmp', 'SF.284.bmp', 'SF.193.bmp', 'SF.378.bmp', 'SF.390.bmp', 'SF.354.bmp', 'SF.329.bmp', 'SF.110.bmp', 'SF.349.bmp', 'SF.5.bmp', 'SF.19.bmp', 'SF.309.bmp', 'SF.383.bmp', 'SF.183.bmp', 'SF.302.bmp', 'SF.406.bmp', 'SF.117.bmp', 'SF.227.bmp', 'SF.243.bmp', 'SF.372.bmp', 'SF.44.bmp', 'SF.337.bmp', 'SF.115.bmp', 'SF.436.bmp', 'SF.265.bmp', 'SF.37.bmp', 'SF.15.bmp', 'SF.400.bmp', 'SF.173.bmp', 'SF.239.bmp', 'SF.331.bmp', 'SF.394.bmp', 'SF.130.bmp', 'SF.137.bmp', 'SF.483.bmp', 'SF.42.bmp', 'SF.88.bmp', 'SF.30.bmp', 'SF.322.bmp', 'SF.328.bmp', 'SF.153.bmp', 'SF.455.bmp', 'SF.444.bmp', 'SF.213.bmp', 'SF.164.bmp', 'SF.9.bmp', 'SF.162.bmp', 'SF.288.bmp', 'SF.293.bmp', 'SF.352.bmp', 'SF.479.bmp', 'SF.133.bmp', 'SF.48.bmp', 'SF.147.bmp', 'SF.86.bmp', 'SF.51.bmp', 'SF.296.bmp', 'SF.353.bmp', 'SF.374.bmp', 'SF.112.bmp', 'SF.283.bmp', 'SF.276.bmp', 'SF.241.bmp', 'SF.259.bmp', 'SF.242.bmp', 'SF.457.bmp', 'SF.56.bmp', 'SF.13.bmp', 'SF.477.bmp', 'SF.24.bmp', 'SF.200.bmp', 'SF.208.bmp', 'SF.380.bmp', 'SF.318.bmp', 'SF.433.bmp', 'SF.238.bmp', 'SF.95.bmp', 'SF.156.bmp', 'SF.101.bmp', 'SF.189.bmp', 'SF.325.bmp', 'SF.474.bmp', 'SF.419.bmp', 'SF.282.bmp', 'SF.291.bmp', 'SF.4.bmp', 'SF.300.bmp', 'SF.431.bmp', 'SF.152.bmp', 'SF.297.bmp', 'SF.144.bmp', 'SF.475.bmp', 'SF.264.bmp', 'SF.377.bmp', 'SF.43.bmp', 'SF.203.bmp', 'SF.425.bmp', 'SF.214.bmp', 'SF.365.bmp', 'SF.333.bmp', 'SF.279.bmp', 'SF.138.bmp', 'SF.176.bmp', 'SF.21.bmp', 'SF.220.bmp', 'SF.150.bmp', 'SF.414.bmp', 'SF.272.bmp', 'SF.367.bmp', 'SF.350.bmp', 'SF.70.bmp', 'SF.448.bmp', 'SF.346.bmp', 'SF.87.bmp', 'SF.361.bmp', 'SF.113.bmp', 'SF.103.bmp', 'SF.67.bmp', 'SF.305.bmp', 'SF.468.bmp', 'SF.129.bmp', 'SF.75.bmp', 'SF.370.bmp', 'SF.119.bmp', 'SF.280.bmp', 'SF.401.bmp', 'SF.428.bmp', 'SF.270.bmp', 'SF.194.bmp', 'SF.252.bmp', 'SF.116.bmp', 'SF.92.bmp', 'SF.65.bmp', 'SF.373.bmp', 'SF.14.bmp', 'SF.210.bmp', 'SF.363.bmp', 'SF.204.bmp', 'SF.108.bmp', 'SF.197.bmp', 'SF.473.bmp', 'SF.102.bmp', 'SF.459.bmp', 'SF.209.bmp', 'SF.217.bmp', 'SF.107.bmp', 'SF.3.bmp', 'SF.488.bmp', 'SF.186.bmp', 'SF.379.bmp', 'SF.271.bmp', 'SF.357.bmp', 'SF.304.bmp', 'SF.301.bmp', 'SF.237.bmp', 'SF.157.bmp', 'SF.396.bmp', 'SF.106.bmp', 'SF.218.bmp', 'SF.434.bmp', 'SF.359.bmp', 'SF.93.bmp', 'SF.319.bmp', 'SF.98.bmp', 'SF.339.bmp', 'SF.466.bmp', 'SF.72.bmp', 'SF.244.bmp', 'SF.83.bmp', 'SF.146.bmp', 'SF.54.bmp', 'SF.426.bmp', 'SF.23.bmp', 'SF.45.bmp', 'SF.148.bmp', 'SF.126.bmp', 'SF.368.bmp', 'SF.151.bmp', 'SF.59.bmp', 'SF.12.bmp', 'SF.371.bmp', 'SF.294.bmp', 'SF.100.bmp', 'SF.412.bmp', 'SF.165.bmp', 'SF.121.bmp', 'SF.78.bmp', 'SF.382.bmp', 'SF.347.bmp', 'SF.168.bmp', 'SF.216.bmp', 'SF.315.bmp', 'SF.132.bmp', 'SF.175.bmp', 'SF.388.bmp', 'SF.418.bmp', 'SF.424.bmp', 'SF.234.bmp', 'SF.462.bmp', 'SF.248.bmp', 'SF.232.bmp', 'SF.311.bmp', 'SF.55.bmp', 'SF.77.bmp', 'SF.47.bmp', 'SF.179.bmp', 'SF.25.bmp', 'SF.422.bmp', 'SF.432.bmp', 'SF.224.bmp', 'SF.1.bmp', 'SF.413.bmp', 'SF.389.bmp', 'SF.50.bmp', 'SF.442.bmp', 'SF.286.bmp', 'SF.376.bmp', 'SF.427.bmp', 'SF.198.bmp', 'SF.275.bmp', 'SF.391.bmp', 'SF.420.bmp', 'SF.277.bmp', 'SF.458.bmp', 'SF.69.bmp', 'SF.205.bmp', 'SF.481.bmp', 'SF.471.bmp']\n",
      "147\n",
      "['SF.445.bmp', 'SF.32.bmp', 'SF.255.bmp', 'SF.461.bmp', 'SF.485.bmp', 'SF.472.bmp', 'SF.139.bmp', 'SF.268.bmp', 'SF.123.bmp', 'SF.344.bmp', 'SF.253.bmp', 'SF.199.bmp', 'SF.81.bmp', 'SF.438.bmp', 'SF.207.bmp', 'SF.229.bmp', 'SF.454.bmp', 'SF.40.bmp', 'SF.124.bmp', 'SF.163.bmp', 'SF.169.bmp', 'SF.439.bmp', 'SF.364.bmp', 'SF.278.bmp', 'SF.437.bmp', 'SF.480.bmp', 'SF.222.bmp', 'SF.470.bmp', 'SF.177.bmp', 'SF.245.bmp', 'SF.90.bmp', 'SF.452.bmp', 'SF.332.bmp', 'SF.449.bmp', 'SF.84.bmp', 'SF.410.bmp', 'SF.446.bmp', 'SF.136.bmp', 'SF.61.bmp', 'SF.91.bmp', 'SF.267.bmp', 'SF.316.bmp', 'SF.73.bmp', 'SF.140.bmp', 'SF.348.bmp', 'SF.125.bmp', 'SF.228.bmp', 'SF.212.bmp', 'SF.221.bmp', 'SF.178.bmp', 'SF.97.bmp', 'SF.85.bmp', 'SF.226.bmp', 'SF.49.bmp', 'SF.430.bmp', 'SF.397.bmp', 'SF.57.bmp', 'SF.404.bmp', 'SF.307.bmp', 'SF.191.bmp', 'SF.290.bmp', 'SF.196.bmp', 'SF.76.bmp', 'SF.41.bmp', 'SF.273.bmp', 'SF.36.bmp', 'SF.336.bmp', 'SF.310.bmp', 'SF.154.bmp', 'SF.254.bmp', 'SF.487.bmp', 'SF.18.bmp', 'SF.429.bmp', 'SF.211.bmp', 'SF.114.bmp', 'SF.80.bmp', 'SF.256.bmp', 'SF.166.bmp', 'SF.299.bmp', 'SF.476.bmp', 'SF.33.bmp', 'SF.240.bmp', 'SF.223.bmp', 'SF.230.bmp', 'SF.281.bmp', 'SF.341.bmp', 'SF.160.bmp', 'SF.443.bmp', 'SF.395.bmp', 'SF.187.bmp', 'SF.274.bmp', 'SF.450.bmp', 'SF.99.bmp', 'SF.447.bmp', 'SF.120.bmp', 'SF.2.bmp', 'SF.298.bmp', 'SF.465.bmp', 'SF.192.bmp', 'SF.314.bmp', 'SF.225.bmp', 'SF.122.bmp', 'SF.463.bmp', 'SF.393.bmp', 'SF.453.bmp', 'SF.68.bmp', 'SF.185.bmp', 'SF.338.bmp', 'SF.155.bmp', 'SF.351.bmp', 'SF.89.bmp', 'SF.233.bmp', 'SF.134.bmp', 'SF.345.bmp', 'SF.423.bmp', 'SF.145.bmp', 'SF.464.bmp', 'SF.195.bmp', 'SF.181.bmp', 'SF.308.bmp', 'SF.71.bmp', 'SF.440.bmp', 'SF.215.bmp', 'SF.22.bmp', 'SF.184.bmp', 'SF.381.bmp', 'SF.127.bmp', 'SF.38.bmp', 'SF.167.bmp', 'SF.250.bmp', 'SF.266.bmp', 'SF.17.bmp', 'SF.82.bmp', 'SF.415.bmp', 'SF.334.bmp', 'SF.142.bmp', 'SF.421.bmp', 'SF.251.bmp', 'SF.416.bmp', 'SF.236.bmp', 'SF.105.bmp', 'SF.7.bmp', 'SF.295.bmp', 'SF.247.bmp', 'SF.219.bmp', 'SF.16.bmp', 'SF.292.bmp']\n",
      "45\n",
      "['tmp.28.bmp', 'tmp.56.bmp', 'tmp.32.bmp', 'tmp.50.bmp', 'tmp.51.bmp', 'tmp.6.bmp', 'tmp.49.bmp', 'tmp.47.bmp', 'tmp.45.bmp', 'tmp.60.bmp', 'tmp.37.bmp', 'tmp.5.bmp', 'tmp.39.bmp', 'tmp.43.bmp', 'tmp.3.bmp', 'tmp.35.bmp', 'tmp.42.bmp', 'tmp.57.bmp', 'tmp.34.bmp', 'tmp.1.bmp', 'tmp.12.bmp', 'tmp.19.bmp', 'tmp.25.bmp', 'tmp.7.bmp', 'tmp.58.bmp', 'tmp.14.bmp', 'tmp.44.bmp', 'tmp.46.bmp', 'tmp.59.bmp', 'tmp.9.bmp', 'tmp.21.bmp', 'tmp.11.bmp', 'tmp.55.bmp', 'tmp.16.bmp', 'tmp.18.bmp', 'tmp.40.bmp', 'tmp.24.bmp', 'tmp.22.bmp', 'tmp.38.bmp', 'tmp.8.bmp', 'tmp.26.bmp', 'tmp.48.bmp', 'tmp.23.bmp', 'tmp.15.bmp', 'tmp.4.bmp']\n",
      "18\n",
      "['tmp.20.bmp', 'tmp.53.bmp', 'tmp.33.bmp', 'tmp.61.bmp', 'tmp.10.bmp', 'tmp.41.bmp', 'tmp.17.bmp', 'tmp.54.bmp', 'tmp.31.bmp', 'tmp.52.bmp', 'tmp.29.bmp', 'tmp.13.bmp', 'tmp.0.bmp', 'tmp.62.bmp', 'tmp.30.bmp', 'tmp.27.bmp', 'tmp.2.bmp', 'tmp.36.bmp']\n"
     ]
    }
   ],
   "source": [
    "\n",
    "\n",
    "from tensorflow.keras.preprocessing.image import ImageDataGenerator\n",
    "# randomize order of image locations\n",
    "# this will help model in not being data sequence dependent\n",
    "\n",
    "# Copy first CM images to train_lion_dir\n",
    "import random\n",
    "    \n",
    "    \n",
    "\n",
    "# Copy first TM images to train_tm_dir # \n",
    "fnames = ['LS.{}.bmp'.format(i) for i in range(103)]\n",
    "random.seed(10)\n",
    "random.shuffle(fnames)\n",
    "fnamest = fnames[0:75]\n",
    "print(len(fnamest))\n",
    "print(fnamest)\n",
    "\n",
    "for fname in fnamest:\n",
    "    src = os.path.join(original_dir, fname)\n",
    "    dst = os.path.join(train_tm_dir, fname)\n",
    "    shutil.copyfile(src, dst)\n",
    "        \n",
    "# Copy next TM images to validation_tm_dir\n",
    "fnamesv = fnames[75:103]\n",
    "print(len(fnamesv))\n",
    "print(fnamesv)\n",
    "for fname in fnamesv:\n",
    "    src = os.path.join(original_dir, fname)\n",
    "    dst = os.path.join(validation_tm_dir, fname)\n",
    "    shutil.copyfile(src, dst)\n",
    "         \n",
    "        \n",
    "    \n",
    "# Copy first CM images to train_cm_dir # \n",
    "fnames = ['SF.{}.bmp'.format(i) for i in range(489)]\n",
    "random.seed(10)\n",
    "random.shuffle(fnames)\n",
    "fnamest = fnames[0:342]\n",
    "print(len(fnamest))\n",
    "print(fnamest)\n",
    "\n",
    "for fname in fnamest:\n",
    "    src = os.path.join(original_dir, fname)\n",
    "    dst = os.path.join(train_cm_dir, fname)\n",
    "    shutil.copyfile(src, dst)\n",
    "        \n",
    "# Copy next CM images to validation_cm_dir\n",
    "fnamesv = fnames[342:489]\n",
    "print(len(fnamesv))\n",
    "print(fnamesv)\n",
    "for fname in fnamesv:\n",
    "    src = os.path.join(original_dir, fname)\n",
    "    dst = os.path.join(validation_cm_dir, fname)\n",
    "    shutil.copyfile(src, dst)\n",
    "\n",
    "\n",
    "# Copy first tramp images to train_tramp_dir # \n",
    "fnames = ['tmp.{}.bmp'.format(i) for i in range(63)]\n",
    "random.seed(10)\n",
    "random.shuffle(fnames)\n",
    "fnamest = fnames[0:45]\n",
    "print(len(fnamest))\n",
    "print(fnamest)\n",
    "\n",
    "for fname in fnamest:\n",
    "    src = os.path.join(original_dir, fname)\n",
    "    dst = os.path.join(train_tramp_dir, fname)\n",
    "    shutil.copyfile(src, dst)\n",
    "        \n",
    "# Copy next TM images to validation_cm_dir\n",
    "fnamesv = fnames[45:63]\n",
    "print(len(fnamesv))\n",
    "print(fnamesv)\n",
    "for fname in fnamesv:\n",
    "    src = os.path.join(original_dir, fname)\n",
    "    dst = os.path.join(validation_tramp_dir, fname)\n",
    "    shutil.copyfile(src, dst)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "efe09e3f",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": [
    "#STAGE 1: training and validation sets"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "02b116cf",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4e98e48e",
   "metadata": {},
   "outputs": [],
   "source": [
    "from tensorflow.keras.applications.vgg16 import preprocess_input #for VGG16\n",
    "#from tensorflow.keras.applications.imagenet_utils import decode_predictions, preprocess_input#for efficient Net\n",
    "\n",
    "#Let's train our network using data augmentation and dropout:\n",
    "train_datagen = ImageDataGenerator(\n",
    "    rotation_range=40,\n",
    "    width_shift_range=0.2,\n",
    "    height_shift_range=0.2,\n",
    "    shear_range=0.2,\n",
    "    zoom_range=0.2,\n",
    "    horizontal_flip=True,\n",
    "    preprocessing_function= \\\n",
    "    keras.applications.vgg16.preprocess_input)#OJO!!!! imagenet_utils for Efficient Net\n",
    "\n",
    "# Note that the validation data should not be augmented!\n",
    "test_datagen = ImageDataGenerator(preprocessing_function= \\\n",
    "    keras.applications.vgg16.preprocess_input)#OJO!!!\n",
    "\n",
    "train_generator = train_datagen.flow_from_directory(\n",
    "        # This is the target directory\n",
    "        train_dir,\n",
    "        # All images will be resized \n",
    "        target_size=(80, 400),\n",
    "        batch_size=32,\n",
    "        class_mode='categorical')#put \"categorical\" si son más de 2 categorías\n",
    "\n",
    "validation_generator = test_datagen.flow_from_directory(\n",
    "        validation_dir,\n",
    "        target_size=(80, 400),\n",
    "        batch_size=32,\n",
    "        class_mode='categorical',\n",
    "        shuffle=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "62521d75",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "af7b5cdc",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": [
    "#MODULE 2: CHOOSE MODEL"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0d1d0fac",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": [
    "# For adding new activation function\n",
    "from keras.layers import Activation\n",
    "from keras import backend as K\n",
    "from tensorflow.python.keras.utils.generic_utils import get_custom_objects\n",
    "#from keras.utils.generic_utils import get_custom_objects\n",
    "def swish(x):\n",
    "    return (K.sigmoid(x) * x)\n",
    "\n",
    "get_custom_objects().update({'swish': Activation(swish)})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "566a8bbd",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": [
    "#activate Dropout and Early Stopping"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "c036d7f7",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING:absl:`lr` is deprecated in Keras optimizer, please use `learning_rate` or use the legacy optimizer, e.g.,tf.keras.optimizers.legacy.Adagrad.\n"
     ]
    }
   ],
   "source": [
    "# Vgg16 for transfer learning with Dropout\n",
    "import sys\n",
    "\n",
    "from matplotlib import pyplot\n",
    "from tensorflow.keras.applications.vgg16 import VGG16\n",
    "from keras.models import Model\n",
    "from keras.layers import Dense\n",
    "from keras.layers import Flatten\n",
    "from keras.layers import Dropout\n",
    "#from keras.optimizers import SGD\n",
    "from keras.optimizers import Adagrad\n",
    "from keras.preprocessing.image import ImageDataGenerator\n",
    "\n",
    "\n",
    "\n",
    "# define cnn model\n",
    "def define_model():\n",
    "\t# load model\n",
    "\tmodel = VGG16(include_top=False, input_shape=(80, 400, 3))\n",
    "\t# mark loaded layers as not trainable\n",
    "\tfor layer in model.layers:\n",
    "\t\tlayer.trainable = False\n",
    "\t# add new classifier layers\n",
    "\tflat1 = Flatten()(model.layers[-1].output)\n",
    "\tclass1 = Dense(128, activation='swish', kernel_initializer='he_uniform')(flat1)\n",
    "\tdrop=Dropout(0.3)(class1)\n",
    "\toutput = Dense(3, activation='softmax')(drop)\n",
    "\t# define new model\n",
    "\tmodel = Model(inputs=model.inputs, outputs=output)\n",
    "\t# compile model\n",
    "\t#opt = SGD(lr=0.001, momentum=0.9)\n",
    "\topt = Adagrad (lr=0.001)\n",
    "\tmodel.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['accuracy'])\n",
    "\treturn model\n",
    "\n",
    "# define model\n",
    "model = define_model()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "50a09b9a",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6fc143a8",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": [
    "#RUN MODEL:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d401c3cf",
   "metadata": {},
   "outputs": [],
   "source": [
    "# define model\n",
    "history = model.fit_generator(\n",
    "      train_generator,\n",
    "      steps_per_epoch=15,\n",
    "      epochs=100,\n",
    "      validation_data=validation_generator,\n",
    "      validation_steps=6)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "d9dc8b61",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\oscar\\AppData\\Local\\Temp\\ipykernel_79084\\1979981568.py:1: UserWarning: `Model.predict_generator` is deprecated and will be removed in a future version. Please use `Model.predict`, which supports generators.\n",
      "  predictions=model.predict_generator(validation_generator, steps=len(validation_generator), verbose=1)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "7/7 [==============================] - 5s 748ms/step\n"
     ]
    }
   ],
   "source": [
    "predictions=model.predict_generator(validation_generator, steps=len(validation_generator), verbose=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "ee267bdf",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "val_preds = np.round(predictions)# from probabilities to 0 or 1\n",
    "val_trues = validation_generator.classes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8890e267",
   "metadata": {},
   "outputs": [],
   "source": [
    "#modify:\n",
    "from sklearn.metrics import confusion_matrix\n",
    "from sklearn.metrics import classification_report\n",
    "import numpy as np\n",
    "num_of_test_samples = 193 #number of testing samples\n",
    "batch_size=32 #batch size for the validation set\n",
    "Y_pred = model.predict_generator(validation_generator, num_of_test_samples // batch_size+1)\n",
    "y_pred = np.argmax(Y_pred, axis=1)\n",
    "print('Confusion Matrix')\n",
    "print(confusion_matrix(validation_generator.classes, y_pred))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1fb02f25",
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Classification Report')\n",
    "target_names = [ \"tm\",'cm', 'tmp']\n",
    "print(classification_report(validation_generator.classes, y_pred, target_names=target_names))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "aa8f5d43",
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "#PLOT\n",
    "acc = history.history['accuracy']\n",
    "val_acc = history.history['val_accuracy']\n",
    "loss = history.history['loss']\n",
    "val_loss = history.history['val_loss']\n",
    "\n",
    "epochs = range(len(acc))\n",
    "\n",
    "plt.plot(epochs, acc, 'bo', label='Training acc')\n",
    "plt.plot(epochs, val_acc, 'b', label='Validation acc')\n",
    "plt.title('Training and validation accuracy')\n",
    "plt.legend()\n",
    "\n",
    "plt.figure()\n",
    "\n",
    "plt.plot(epochs, loss, 'bo', label='Training loss')\n",
    "plt.plot(epochs, val_loss, 'b', label='Validation loss')\n",
    "plt.title('Training and validation loss')\n",
    "plt.legend()\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6313546d-b7db-4acc-95b2-ae24f2948b7e",
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# Extract values\n",
    "acc = history.history['accuracy']\n",
    "val_acc = history.history['val_accuracy']\n",
    "loss = history.history['loss']\n",
    "val_loss = history.history['val_loss']\n",
    "epochs = range(len(acc))\n",
    "\n",
    "# Accuracy Plot\n",
    "plt.plot(epochs, acc, 'ro', label='Training acc')       # red dots\n",
    "plt.plot(epochs, val_acc, 'g-', label='Validation acc') # green line\n",
    "plt.title('Training and validation accuracy')\n",
    "plt.xlabel('Epochs')\n",
    "plt.ylabel('Accuracy')\n",
    "plt.legend()\n",
    "\n",
    "plt.figure()\n",
    "\n",
    "# Loss Plot\n",
    "plt.plot(epochs, loss, 'ro', label='Training loss')       # red dots\n",
    "plt.plot(epochs, val_loss, 'g-', label='Validation loss') # green line\n",
    "plt.title('Training and validation loss')\n",
    "plt.xlabel('Epochs')\n",
    "plt.ylabel('Loss')\n",
    "plt.legend()\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ad6ed72d",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": [
    "from sklearn.metrics import roc_auc_score\n",
    "from sklearn.preprocessing import label_binarize\n",
    "\n",
    "# Step 1: Binarize the true labels\n",
    "val_trues_bin = label_binarize(val_trues, classes=[0, 1, 2])  # assuming classes are 0, 1, 2\n",
    "\n",
    "# Step 2: Calculate ROC-AUC\n",
    "roc_auc = roc_auc_score(val_trues_bin, predictions, average='macro', multi_class='ovr')\n",
    "\n",
    "print(f'Multi-class ROC-AUC: {roc_auc:.4f}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "035f7df1-6b39-48e9-9e91-3b6ed7bf310d",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn.metrics import roc_curve, auc\n",
    "from sklearn.preprocessing import label_binarize\n",
    "from sklearn.metrics import roc_auc_score\n",
    "\n",
    "# Step 1: Binarize the true labels\n",
    "n_classes = predictions.shape[1]  # should be 3\n",
    "val_trues_bin = label_binarize(val_trues, classes=[0, 1, 2])  # shape: (n_samples, n_classes)\n",
    "\n",
    "# Step 2: Compute FPR, TPR, and ROC AUC for each class\n",
    "fpr = dict()\n",
    "tpr = dict()\n",
    "roc_auc = dict()\n",
    "for i in range(n_classes):\n",
    "    fpr[i], tpr[i], _ = roc_curve(val_trues_bin[:, i], predictions[:, i])\n",
    "    roc_auc[i] = auc(fpr[i], tpr[i])\n",
    "\n",
    "# Step 3: Compute macro-average ROC curve\n",
    "# First aggregate all false positive rates\n",
    "all_fpr = np.unique(np.concatenate([fpr[i] for i in range(n_classes)]))\n",
    "\n",
    "# Then interpolate all ROC curves at these points\n",
    "mean_tpr = np.zeros_like(all_fpr)\n",
    "for i in range(n_classes):\n",
    "    mean_tpr += np.interp(all_fpr, fpr[i], tpr[i])\n",
    "\n",
    "# Average it and compute AUC\n",
    "mean_tpr /= n_classes\n",
    "fpr[\"macro\"] = all_fpr\n",
    "tpr[\"macro\"] = mean_tpr\n",
    "roc_auc[\"macro\"] = auc(fpr[\"macro\"], tpr[\"macro\"])\n",
    "\n",
    "# Step 4: Plot\n",
    "plt.figure(figsize=(8, 6))\n",
    "colors = ['blue', 'green', 'red']\n",
    "for i in range(n_classes):\n",
    "    plt.plot(fpr[i], tpr[i], color=colors[i], lw=2,\n",
    "             label=f'Class {i} (AUC = {roc_auc[i]:.2f})')\n",
    "\n",
    "plt.plot(fpr[\"macro\"], tpr[\"macro\"], color='darkorange', linestyle='--',\n",
    "         label=f'Macro-average (AUC = {roc_auc[\"macro\"]:.2f})', lw=2)\n",
    "\n",
    "plt.plot([0, 1], [0, 1], 'k--', lw=1)\n",
    "plt.xlim([0.0, 1.0])\n",
    "plt.ylim([0.0, 1.05])\n",
    "plt.xlabel('False Positive Rate')\n",
    "plt.ylabel('True Positive Rate')\n",
    "plt.title('Multi-Class ROC Curve')\n",
    "plt.legend(loc='lower right')\n",
    "plt.grid()\n",
    "plt.tight_layout()\n",
    "\n",
    "# 💾 Save the figure before displaying\n",
    "#plt.savefig(\"roc_curve.png\", dpi=300)  # You can change the filename and format if needed\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "34cbf1e7",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6d2d3091",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5bbec4e9",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5d4730ab",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "77f7809c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# STAGE 2: with grayscale intensity agnostic:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "989bbd75",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "from tensorflow.keras.applications.vgg16 import preprocess_input\n",
    "import cv2\n",
    "\n",
    "def random_3channel_grayscale_augmentations(image):\n",
    "    # Ensure the input is a 3-channel grayscale image\n",
    "    if len(image.shape) != 3 or image.shape[2] != 3:\n",
    "        raise ValueError(\"Input image must be a 3-channel grayscale image.\")\n",
    "    \n",
    "    # Ensure image values are in the correct range\n",
    "    image = np.clip(image, 0, 255).astype(np.uint8)\n",
    "    \n",
    "    # Process each channel independently\n",
    "    for channel in range(3):\n",
    "        random_value = np.random.randint(0, 3)  # Randomly choose between brightness, contrast, and sharpening\n",
    "        \n",
    "        if random_value == 0:\n",
    "            # Randomly adjust brightness\n",
    "            brightness_scale = np.random.uniform(0.8, 1.2)\n",
    "            image[:, :, channel] = np.clip(image[:, :, channel] * brightness_scale, 0, 255).astype(np.uint8)\n",
    "        elif random_value == 1:\n",
    "            # Randomly adjust contrast\n",
    "            contrast_scale = np.random.uniform(0.8, 1.2)\n",
    "            mean_intensity = np.mean(image[:, :, channel])\n",
    "            image[:, :, channel] = np.clip((image[:, :, channel] - mean_intensity) * contrast_scale + mean_intensity, 0, 255).astype(np.uint8)\n",
    "        else:\n",
    "            # Apply sharpening\n",
    "            kernel = np.array([[0, -1, 0], \n",
    "                               [-1, 5, -1], \n",
    "                               [0, -1, 0]])\n",
    "            image[:, :, channel] = cv2.filter2D(image[:, :, channel], -1, kernel)\n",
    "    \n",
    "    # Convert image to float32 for VGG16 preprocessing\n",
    "    image = image.astype(np.float32)\n",
    "    \n",
    "    # Apply VGG16 preprocessing\n",
    "    augmented_image = preprocess_input(image)\n",
    "    \n",
    "    return augmented_image\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "a0782f37",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Found 462 images belonging to 3 classes.\n",
      "Found 193 images belonging to 3 classes.\n"
     ]
    }
   ],
   "source": [
    "from tensorflow.keras.applications.vgg16 import preprocess_input #for VGG16\n",
    "#from keras.applications.imagenet_utils import decode_predictions, preprocess_input#for efficient Net\n",
    "#from tensorflow.keras.applications.imagenet_utils import decode_predictions, preprocess_input#for efficient Net\n",
    "\n",
    "#Let's train our network using data augmentation and dropout:\n",
    "train_datagen = ImageDataGenerator(\n",
    "    rotation_range=40,\n",
    "    width_shift_range=0.2,\n",
    "    height_shift_range=0.2,\n",
    "    shear_range=0.2,\n",
    "    zoom_range=0.2,\n",
    "    horizontal_flip=True,\n",
    "    preprocessing_function=random_3channel_grayscale_augmentations)\n",
    "\n",
    "# Note that the validation data should not be augmented!\n",
    "test_datagen = ImageDataGenerator(preprocessing_function= \\\n",
    "    keras.applications.vgg16.preprocess_input)#OJO!!!\n",
    "\n",
    "train_generator = train_datagen.flow_from_directory(\n",
    "        # This is the target directory\n",
    "        train_dir,\n",
    "        # All images will be resized \n",
    "        target_size=(80, 400),\n",
    "        batch_size=32,\n",
    "        class_mode='categorical')#put \"categorical\" si son más de 2 categorías\n",
    "\n",
    "validation_generator = test_datagen.flow_from_directory(\n",
    "        validation_dir,\n",
    "        target_size=(80, 400),\n",
    "        batch_size=32,\n",
    "        class_mode='categorical',\n",
    "        shuffle=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "0475771c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# For adding new activation function\n",
    "from keras.layers import Activation\n",
    "from keras import backend as K\n",
    "from tensorflow.python.keras.utils.generic_utils import get_custom_objects\n",
    "#from keras.utils.generic_utils import get_custom_objects\n",
    "def swish(x):\n",
    "    return (K.sigmoid(x) * x)\n",
    "\n",
    "get_custom_objects().update({'swish': Activation(swish)})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "a62e7a74",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Vgg16 for transfer learning with Dropout\n",
    "import sys\n",
    "\n",
    "from matplotlib import pyplot\n",
    "from tensorflow.keras.applications.vgg16 import VGG16\n",
    "from keras.models import Model\n",
    "from keras.layers import Dense\n",
    "from keras.layers import Flatten\n",
    "from keras.layers import Dropout\n",
    "#from keras.optimizers import SGD\n",
    "from keras.optimizers import Adagrad\n",
    "from keras.preprocessing.image import ImageDataGenerator\n",
    "\n",
    "\n",
    "\n",
    "# define cnn model\n",
    "def define_model():\n",
    "\t# load model\n",
    "\tmodel = VGG16(include_top=False, input_shape=(80, 400, 3))\n",
    "\t# mark loaded layers as not trainable\n",
    "\tfor layer in model.layers:\n",
    "\t\tlayer.trainable = False\n",
    "\t# add new classifier layers\n",
    "\tflat1 = Flatten()(model.layers[-1].output)\n",
    "\tclass1 = Dense(128, activation='swish', kernel_initializer='he_uniform')(flat1)\n",
    "\tdrop=Dropout(0.3)(class1)\n",
    "\toutput = Dense(3, activation='softmax')(drop)\n",
    "\t# define new model\n",
    "\tmodel = Model(inputs=model.inputs, outputs=output)\n",
    "\t# compile model\n",
    "\t#opt = SGD(lr=0.001, momentum=0.9)\n",
    "\topt = Adagrad (lr=0.001)\n",
    "\tmodel.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['accuracy'])\n",
    "\treturn model\n",
    "\n",
    "# define model\n",
    "model = define_model()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "570b0d28",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\IDEA\\Anaconda3\\envs\\gputest\\lib\\site-packages\\tensorflow\\python\\keras\\engine\\training.py:1844: UserWarning: `Model.fit_generator` is deprecated and will be removed in a future version. Please use `Model.fit`, which supports generators.\n",
      "  warnings.warn('`Model.fit_generator` is deprecated and '\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/100\n",
      "14/14 [==============================] - 20s 1s/step - loss: 6.3234 - accuracy: 0.6345 - val_loss: 0.6764 - val_accuracy: 0.8229\n",
      "Epoch 2/100\n",
      "14/14 [==============================] - 8s 556ms/step - loss: 1.4947 - accuracy: 0.7954 - val_loss: 0.5139 - val_accuracy: 0.8385\n",
      "Epoch 3/100\n",
      "14/14 [==============================] - 8s 542ms/step - loss: 0.9049 - accuracy: 0.8169 - val_loss: 0.4470 - val_accuracy: 0.8490\n",
      "Epoch 4/100\n",
      "14/14 [==============================] - 8s 552ms/step - loss: 0.7123 - accuracy: 0.8426 - val_loss: 0.4444 - val_accuracy: 0.8594\n",
      "Epoch 5/100\n",
      "14/14 [==============================] - 8s 549ms/step - loss: 0.8732 - accuracy: 0.8341 - val_loss: 0.4090 - val_accuracy: 0.8646\n",
      "Epoch 6/100\n",
      "14/14 [==============================] - 8s 555ms/step - loss: 0.7483 - accuracy: 0.8388 - val_loss: 0.4580 - val_accuracy: 0.8542\n",
      "Epoch 7/100\n",
      "14/14 [==============================] - 8s 557ms/step - loss: 0.5809 - accuracy: 0.8724 - val_loss: 0.3795 - val_accuracy: 0.8750\n",
      "Epoch 8/100\n",
      "14/14 [==============================] - 8s 558ms/step - loss: 0.4957 - accuracy: 0.8522 - val_loss: 0.3447 - val_accuracy: 0.8854\n",
      "Epoch 9/100\n",
      "14/14 [==============================] - 8s 567ms/step - loss: 0.6054 - accuracy: 0.8644 - val_loss: 0.3226 - val_accuracy: 0.8906\n",
      "Epoch 10/100\n",
      "14/14 [==============================] - 8s 552ms/step - loss: 0.5567 - accuracy: 0.8507 - val_loss: 0.3486 - val_accuracy: 0.8750\n",
      "Epoch 11/100\n",
      "14/14 [==============================] - 8s 560ms/step - loss: 0.5421 - accuracy: 0.8793 - val_loss: 0.3636 - val_accuracy: 0.8698\n",
      "Epoch 12/100\n",
      "14/14 [==============================] - 8s 559ms/step - loss: 0.4887 - accuracy: 0.8464 - val_loss: 0.3747 - val_accuracy: 0.8750\n",
      "Epoch 13/100\n",
      "14/14 [==============================] - 8s 542ms/step - loss: 0.3808 - accuracy: 0.8878 - val_loss: 0.3729 - val_accuracy: 0.8646\n",
      "Epoch 14/100\n",
      "14/14 [==============================] - 8s 576ms/step - loss: 0.3935 - accuracy: 0.8594 - val_loss: 0.3131 - val_accuracy: 0.9010\n",
      "Epoch 15/100\n",
      "14/14 [==============================] - 8s 572ms/step - loss: 0.3646 - accuracy: 0.8817 - val_loss: 0.2907 - val_accuracy: 0.9010\n",
      "Epoch 16/100\n",
      "14/14 [==============================] - 8s 564ms/step - loss: 0.3396 - accuracy: 0.9073 - val_loss: 0.2657 - val_accuracy: 0.9010\n",
      "Epoch 17/100\n",
      "14/14 [==============================] - 8s 567ms/step - loss: 0.3007 - accuracy: 0.8894 - val_loss: 0.2475 - val_accuracy: 0.8958\n",
      "Epoch 18/100\n",
      "14/14 [==============================] - 8s 558ms/step - loss: 0.4125 - accuracy: 0.8640 - val_loss: 0.2341 - val_accuracy: 0.9062\n",
      "Epoch 19/100\n",
      "14/14 [==============================] - 8s 562ms/step - loss: 0.3178 - accuracy: 0.8875 - val_loss: 0.2414 - val_accuracy: 0.9010\n",
      "Epoch 20/100\n",
      "14/14 [==============================] - 8s 553ms/step - loss: 0.3215 - accuracy: 0.9017 - val_loss: 0.2289 - val_accuracy: 0.9115\n",
      "Epoch 21/100\n",
      "14/14 [==============================] - 8s 553ms/step - loss: 0.3218 - accuracy: 0.9049 - val_loss: 0.2446 - val_accuracy: 0.9010\n",
      "Epoch 22/100\n",
      "14/14 [==============================] - 8s 554ms/step - loss: 0.4879 - accuracy: 0.8493 - val_loss: 0.2237 - val_accuracy: 0.9167\n",
      "Epoch 23/100\n",
      "14/14 [==============================] - 8s 560ms/step - loss: 0.2895 - accuracy: 0.8777 - val_loss: 0.2228 - val_accuracy: 0.9115\n",
      "Epoch 24/100\n",
      "14/14 [==============================] - 8s 544ms/step - loss: 0.2811 - accuracy: 0.9084 - val_loss: 0.2387 - val_accuracy: 0.9062\n",
      "Epoch 25/100\n",
      "14/14 [==============================] - 8s 550ms/step - loss: 0.3214 - accuracy: 0.8807 - val_loss: 0.2423 - val_accuracy: 0.9115\n",
      "Epoch 26/100\n",
      "14/14 [==============================] - 8s 549ms/step - loss: 0.2887 - accuracy: 0.8902 - val_loss: 0.2543 - val_accuracy: 0.9062\n",
      "Epoch 27/100\n",
      "14/14 [==============================] - 8s 544ms/step - loss: 0.3122 - accuracy: 0.8887 - val_loss: 0.2452 - val_accuracy: 0.9115\n",
      "Epoch 28/100\n",
      "14/14 [==============================] - 8s 556ms/step - loss: 0.3051 - accuracy: 0.8852 - val_loss: 0.2475 - val_accuracy: 0.9115\n",
      "Epoch 29/100\n",
      "14/14 [==============================] - 8s 554ms/step - loss: 0.2960 - accuracy: 0.8878 - val_loss: 0.2376 - val_accuracy: 0.9115\n",
      "Epoch 30/100\n",
      "14/14 [==============================] - 8s 554ms/step - loss: 0.2337 - accuracy: 0.9179 - val_loss: 0.2267 - val_accuracy: 0.9115\n",
      "Epoch 31/100\n",
      "14/14 [==============================] - 8s 564ms/step - loss: 0.2842 - accuracy: 0.8964 - val_loss: 0.2286 - val_accuracy: 0.9115\n",
      "Epoch 32/100\n",
      "14/14 [==============================] - 8s 572ms/step - loss: 0.2674 - accuracy: 0.9092 - val_loss: 0.2065 - val_accuracy: 0.9219\n",
      "Epoch 33/100\n",
      "14/14 [==============================] - 8s 550ms/step - loss: 0.2361 - accuracy: 0.9298 - val_loss: 0.2197 - val_accuracy: 0.9219\n",
      "Epoch 34/100\n",
      "14/14 [==============================] - 8s 551ms/step - loss: 0.3964 - accuracy: 0.8876 - val_loss: 0.2165 - val_accuracy: 0.9219\n",
      "Epoch 35/100\n",
      "14/14 [==============================] - 8s 562ms/step - loss: 0.2931 - accuracy: 0.9048 - val_loss: 0.2299 - val_accuracy: 0.9219\n",
      "Epoch 36/100\n",
      "14/14 [==============================] - 8s 548ms/step - loss: 0.2651 - accuracy: 0.9217 - val_loss: 0.2231 - val_accuracy: 0.9167\n",
      "Epoch 37/100\n",
      "14/14 [==============================] - 8s 546ms/step - loss: 0.3404 - accuracy: 0.8647 - val_loss: 0.2009 - val_accuracy: 0.9271\n",
      "Epoch 38/100\n",
      "14/14 [==============================] - 8s 566ms/step - loss: 0.2099 - accuracy: 0.9081 - val_loss: 0.2037 - val_accuracy: 0.9271\n",
      "Epoch 39/100\n",
      "14/14 [==============================] - 8s 549ms/step - loss: 0.3151 - accuracy: 0.8855 - val_loss: 0.2009 - val_accuracy: 0.9271\n",
      "Epoch 40/100\n",
      "14/14 [==============================] - 8s 569ms/step - loss: 0.2939 - accuracy: 0.9046 - val_loss: 0.1989 - val_accuracy: 0.9271\n",
      "Epoch 41/100\n",
      "14/14 [==============================] - 8s 553ms/step - loss: 0.2646 - accuracy: 0.9093 - val_loss: 0.1885 - val_accuracy: 0.9323\n",
      "Epoch 42/100\n",
      "14/14 [==============================] - 8s 542ms/step - loss: 0.2209 - accuracy: 0.9231 - val_loss: 0.1884 - val_accuracy: 0.9323\n",
      "Epoch 43/100\n",
      "14/14 [==============================] - 8s 552ms/step - loss: 0.2686 - accuracy: 0.9164 - val_loss: 0.1835 - val_accuracy: 0.9323\n",
      "Epoch 44/100\n",
      "14/14 [==============================] - 8s 554ms/step - loss: 0.3050 - accuracy: 0.9014 - val_loss: 0.1908 - val_accuracy: 0.9271\n",
      "Epoch 45/100\n",
      "14/14 [==============================] - 8s 549ms/step - loss: 0.2782 - accuracy: 0.8968 - val_loss: 0.1986 - val_accuracy: 0.9167\n",
      "Epoch 46/100\n",
      "14/14 [==============================] - 8s 557ms/step - loss: 0.2842 - accuracy: 0.9055 - val_loss: 0.2015 - val_accuracy: 0.9167\n",
      "Epoch 47/100\n",
      "14/14 [==============================] - 8s 551ms/step - loss: 0.3046 - accuracy: 0.8927 - val_loss: 0.1866 - val_accuracy: 0.9323\n",
      "Epoch 48/100\n",
      "14/14 [==============================] - 8s 554ms/step - loss: 0.2682 - accuracy: 0.9011 - val_loss: 0.1825 - val_accuracy: 0.9323\n",
      "Epoch 49/100\n",
      "14/14 [==============================] - 8s 554ms/step - loss: 0.2858 - accuracy: 0.9070 - val_loss: 0.1847 - val_accuracy: 0.9323\n",
      "Epoch 50/100\n",
      "14/14 [==============================] - 8s 543ms/step - loss: 0.2517 - accuracy: 0.8942 - val_loss: 0.2021 - val_accuracy: 0.9219\n",
      "Epoch 51/100\n",
      "14/14 [==============================] - 8s 556ms/step - loss: 0.2698 - accuracy: 0.8973 - val_loss: 0.2014 - val_accuracy: 0.9167\n",
      "Epoch 52/100\n",
      "14/14 [==============================] - 8s 543ms/step - loss: 0.2369 - accuracy: 0.9107 - val_loss: 0.2092 - val_accuracy: 0.9271\n",
      "Epoch 53/100\n",
      "14/14 [==============================] - 8s 549ms/step - loss: 0.2718 - accuracy: 0.9006 - val_loss: 0.2058 - val_accuracy: 0.9271\n",
      "Epoch 54/100\n",
      "14/14 [==============================] - 8s 569ms/step - loss: 0.2872 - accuracy: 0.8949 - val_loss: 0.2059 - val_accuracy: 0.9271\n",
      "Epoch 55/100\n",
      "14/14 [==============================] - 8s 549ms/step - loss: 0.2272 - accuracy: 0.9149 - val_loss: 0.2006 - val_accuracy: 0.9271\n",
      "Epoch 56/100\n",
      "14/14 [==============================] - 8s 550ms/step - loss: 0.3359 - accuracy: 0.8768 - val_loss: 0.1944 - val_accuracy: 0.9219\n",
      "Epoch 57/100\n",
      "14/14 [==============================] - 8s 546ms/step - loss: 0.2522 - accuracy: 0.9175 - val_loss: 0.1927 - val_accuracy: 0.9271\n",
      "Epoch 58/100\n",
      "14/14 [==============================] - 8s 548ms/step - loss: 0.2209 - accuracy: 0.9280 - val_loss: 0.1916 - val_accuracy: 0.9271\n",
      "Epoch 59/100\n",
      "14/14 [==============================] - 8s 554ms/step - loss: 0.2799 - accuracy: 0.9368 - val_loss: 0.2042 - val_accuracy: 0.9167\n",
      "Epoch 60/100\n",
      "14/14 [==============================] - 8s 550ms/step - loss: 0.2128 - accuracy: 0.9413 - val_loss: 0.1986 - val_accuracy: 0.9219\n",
      "Epoch 61/100\n",
      "14/14 [==============================] - 8s 551ms/step - loss: 0.2282 - accuracy: 0.9230 - val_loss: 0.1880 - val_accuracy: 0.9219\n",
      "Epoch 62/100\n",
      "14/14 [==============================] - 8s 585ms/step - loss: 0.1926 - accuracy: 0.9208 - val_loss: 0.1836 - val_accuracy: 0.9271\n",
      "Epoch 63/100\n",
      "14/14 [==============================] - 8s 573ms/step - loss: 0.2349 - accuracy: 0.9078 - val_loss: 0.1843 - val_accuracy: 0.9323\n",
      "Epoch 64/100\n",
      "14/14 [==============================] - 8s 554ms/step - loss: 0.3089 - accuracy: 0.9128 - val_loss: 0.1787 - val_accuracy: 0.9323\n",
      "Epoch 65/100\n",
      "14/14 [==============================] - 8s 564ms/step - loss: 0.1826 - accuracy: 0.9186 - val_loss: 0.1666 - val_accuracy: 0.9427\n",
      "Epoch 66/100\n",
      "14/14 [==============================] - 8s 570ms/step - loss: 0.2194 - accuracy: 0.9368 - val_loss: 0.1649 - val_accuracy: 0.9375\n",
      "Epoch 67/100\n",
      "14/14 [==============================] - 8s 562ms/step - loss: 0.1881 - accuracy: 0.9480 - val_loss: 0.1765 - val_accuracy: 0.9323\n",
      "Epoch 68/100\n",
      "14/14 [==============================] - 8s 567ms/step - loss: 0.1927 - accuracy: 0.9433 - val_loss: 0.1701 - val_accuracy: 0.9323\n",
      "Epoch 69/100\n",
      "14/14 [==============================] - 8s 552ms/step - loss: 0.1856 - accuracy: 0.9397 - val_loss: 0.1750 - val_accuracy: 0.9375\n",
      "Epoch 70/100\n",
      "14/14 [==============================] - 8s 541ms/step - loss: 0.1914 - accuracy: 0.9308 - val_loss: 0.1869 - val_accuracy: 0.9271\n",
      "Epoch 71/100\n",
      "14/14 [==============================] - 8s 561ms/step - loss: 0.1984 - accuracy: 0.9296 - val_loss: 0.1886 - val_accuracy: 0.9271\n",
      "Epoch 72/100\n",
      "14/14 [==============================] - 8s 577ms/step - loss: 0.2495 - accuracy: 0.8921 - val_loss: 0.1976 - val_accuracy: 0.9271\n",
      "Epoch 73/100\n",
      "14/14 [==============================] - 8s 549ms/step - loss: 0.1796 - accuracy: 0.9460 - val_loss: 0.1861 - val_accuracy: 0.9271\n",
      "Epoch 74/100\n",
      "14/14 [==============================] - 8s 545ms/step - loss: 0.1887 - accuracy: 0.9276 - val_loss: 0.1867 - val_accuracy: 0.9271\n",
      "Epoch 75/100\n",
      "14/14 [==============================] - 8s 553ms/step - loss: 0.2027 - accuracy: 0.9168 - val_loss: 0.1852 - val_accuracy: 0.9271\n",
      "Epoch 76/100\n",
      "14/14 [==============================] - 8s 556ms/step - loss: 0.2470 - accuracy: 0.9135 - val_loss: 0.1830 - val_accuracy: 0.9323\n",
      "Epoch 77/100\n",
      "14/14 [==============================] - 8s 551ms/step - loss: 0.1782 - accuracy: 0.9348 - val_loss: 0.1673 - val_accuracy: 0.9323\n",
      "Epoch 78/100\n",
      "14/14 [==============================] - 8s 557ms/step - loss: 0.1422 - accuracy: 0.9499 - val_loss: 0.1739 - val_accuracy: 0.9271\n",
      "Epoch 79/100\n",
      "14/14 [==============================] - 8s 543ms/step - loss: 0.2668 - accuracy: 0.9133 - val_loss: 0.1835 - val_accuracy: 0.9219\n",
      "Epoch 80/100\n",
      "14/14 [==============================] - 8s 586ms/step - loss: 0.2579 - accuracy: 0.9159 - val_loss: 0.1763 - val_accuracy: 0.9323\n",
      "Epoch 81/100\n",
      "14/14 [==============================] - 8s 554ms/step - loss: 0.1965 - accuracy: 0.9331 - val_loss: 0.1793 - val_accuracy: 0.9271\n",
      "Epoch 82/100\n",
      "14/14 [==============================] - 8s 567ms/step - loss: 0.2206 - accuracy: 0.9166 - val_loss: 0.1695 - val_accuracy: 0.9323\n",
      "Epoch 83/100\n",
      "14/14 [==============================] - 8s 571ms/step - loss: 0.2430 - accuracy: 0.8979 - val_loss: 0.1627 - val_accuracy: 0.9375\n",
      "Epoch 84/100\n",
      "14/14 [==============================] - 8s 567ms/step - loss: 0.2148 - accuracy: 0.9376 - val_loss: 0.1618 - val_accuracy: 0.9479\n",
      "Epoch 85/100\n",
      "14/14 [==============================] - 8s 557ms/step - loss: 0.2571 - accuracy: 0.9226 - val_loss: 0.1614 - val_accuracy: 0.9479\n",
      "Epoch 86/100\n",
      "14/14 [==============================] - 8s 567ms/step - loss: 0.1868 - accuracy: 0.9289 - val_loss: 0.1585 - val_accuracy: 0.9479\n",
      "Epoch 87/100\n",
      "14/14 [==============================] - 8s 551ms/step - loss: 0.2364 - accuracy: 0.9135 - val_loss: 0.1579 - val_accuracy: 0.9479\n",
      "Epoch 88/100\n",
      "14/14 [==============================] - 8s 547ms/step - loss: 0.1849 - accuracy: 0.9313 - val_loss: 0.1532 - val_accuracy: 0.9531\n",
      "Epoch 89/100\n",
      "14/14 [==============================] - 8s 552ms/step - loss: 0.2205 - accuracy: 0.9397 - val_loss: 0.1589 - val_accuracy: 0.9427\n",
      "Epoch 90/100\n",
      "14/14 [==============================] - 8s 558ms/step - loss: 0.2138 - accuracy: 0.9170 - val_loss: 0.1642 - val_accuracy: 0.9427\n",
      "Epoch 91/100\n",
      "14/14 [==============================] - 8s 567ms/step - loss: 0.1827 - accuracy: 0.9259 - val_loss: 0.1700 - val_accuracy: 0.9323\n",
      "Epoch 92/100\n",
      "14/14 [==============================] - 8s 550ms/step - loss: 0.2456 - accuracy: 0.9171 - val_loss: 0.1665 - val_accuracy: 0.9375\n",
      "Epoch 93/100\n",
      "14/14 [==============================] - 8s 548ms/step - loss: 0.1686 - accuracy: 0.9463 - val_loss: 0.1767 - val_accuracy: 0.9375\n",
      "Epoch 94/100\n",
      "14/14 [==============================] - 8s 568ms/step - loss: 0.1847 - accuracy: 0.9253 - val_loss: 0.1827 - val_accuracy: 0.9323\n",
      "Epoch 95/100\n",
      "14/14 [==============================] - 8s 560ms/step - loss: 0.2112 - accuracy: 0.9117 - val_loss: 0.1705 - val_accuracy: 0.9375\n",
      "Epoch 96/100\n",
      "14/14 [==============================] - 8s 554ms/step - loss: 0.2368 - accuracy: 0.9084 - val_loss: 0.1773 - val_accuracy: 0.9375\n",
      "Epoch 97/100\n",
      "14/14 [==============================] - 8s 562ms/step - loss: 0.1907 - accuracy: 0.9358 - val_loss: 0.1773 - val_accuracy: 0.9375\n",
      "Epoch 98/100\n",
      "14/14 [==============================] - 8s 559ms/step - loss: 0.2260 - accuracy: 0.9041 - val_loss: 0.1699 - val_accuracy: 0.9375\n",
      "Epoch 99/100\n",
      "14/14 [==============================] - 8s 562ms/step - loss: 0.2005 - accuracy: 0.9478 - val_loss: 0.1747 - val_accuracy: 0.9375\n",
      "Epoch 100/100\n",
      "14/14 [==============================] - 8s 547ms/step - loss: 0.2857 - accuracy: 0.9224 - val_loss: 0.1740 - val_accuracy: 0.9375\n"
     ]
    }
   ],
   "source": [
    "# define model\n",
    "history = model.fit_generator(\n",
    "      train_generator,\n",
    "      steps_per_epoch=14,\n",
    "      epochs=100,\n",
    "      validation_data=validation_generator,\n",
    "      validation_steps=6)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0a4894fc",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "id": "8b592169",
   "metadata": {},
   "outputs": [],
   "source": [
    "#save model\n",
    "model_json=model.to_json()\n",
    "with open(\"vgg16-swish-Adagrad_ColorAugmentation.json\", \"w\") as json_file:\n",
    "    json_file.write(model_json)\n",
    "#serializar los pesos\n",
    "model.save_weights(\"vgg16-swish-Adagrad_ColorAugmentation.h5\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "7e6bf47d",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\IDEA\\Anaconda3\\envs\\gputest\\lib\\site-packages\\tensorflow\\python\\keras\\engine\\training.py:1905: UserWarning: `Model.predict_generator` is deprecated and will be removed in a future version. Please use `Model.predict`, which supports generators.\n",
      "  warnings.warn('`Model.predict_generator` is deprecated and '\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "7/7 [==============================] - 2s 185ms/step\n"
     ]
    }
   ],
   "source": [
    "predictions=model.predict_generator(validation_generator, steps=len(validation_generator), verbose=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "9e33be02",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "val_preds = np.argmax(predictions, axis=1)\n",
    "val_trues = validation_generator.classes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "e5bbce44",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\IDEA\\Anaconda3\\envs\\gputest\\lib\\site-packages\\tensorflow\\python\\keras\\engine\\training.py:1905: UserWarning: `Model.predict_generator` is deprecated and will be removed in a future version. Please use `Model.predict`, which supports generators.\n",
      "  warnings.warn('`Model.predict_generator` is deprecated and '\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Confusion Matrix\n",
      "[[ 22   4   2]\n",
      " [  0 147   0]\n",
      " [  2   4  12]]\n"
     ]
    }
   ],
   "source": [
    "#modify:\n",
    "from sklearn.metrics import confusion_matrix\n",
    "from sklearn.metrics import classification_report\n",
    "import numpy as np\n",
    "num_of_test_samples = 193 #number of testing samples\n",
    "batch_size=32 #batch size for the validation set\n",
    "Y_pred = model.predict_generator(validation_generator, num_of_test_samples // batch_size+1)\n",
    "y_pred = np.argmax(Y_pred, axis=1)\n",
    "print('Confusion Matrix')\n",
    "print(confusion_matrix(validation_generator.classes, y_pred))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "aa3967f8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Classification Report\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "          tm       0.92      0.79      0.85        28\n",
      "          cm       0.95      1.00      0.97       147\n",
      "         tmp       0.86      0.67      0.75        18\n",
      "\n",
      "    accuracy                           0.94       193\n",
      "   macro avg       0.91      0.82      0.86       193\n",
      "weighted avg       0.94      0.94      0.93       193\n",
      "\n"
     ]
    }
   ],
   "source": [
    "print('Classification Report')\n",
    "target_names = [ \"tm\",'cm', 'tmp']\n",
    "print(classification_report(validation_generator.classes, y_pred, target_names=target_names))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "a5440067",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\IDEA\\Anaconda3\\envs\\gputest\\lib\\site-packages\\tensorflow\\python\\keras\\engine\\training.py:1905: UserWarning: `Model.predict_generator` is deprecated and will be removed in a future version. Please use `Model.predict`, which supports generators.\n",
      "  warnings.warn('`Model.predict_generator` is deprecated and '\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "7/7 [==============================] - 1s 131ms/step\n",
      "Confusion Matrix\n",
      "[[ 22   4   2]\n",
      " [  0 147   0]\n",
      " [  2   4  12]]\n",
      "Classification Report\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "          LS       0.92      0.79      0.85        28\n",
      "          SF       0.95      1.00      0.97       147\n",
      "         tmp       0.86      0.67      0.75        18\n",
      "\n",
      "    accuracy                           0.94       193\n",
      "   macro avg       0.91      0.82      0.86       193\n",
      "weighted avg       0.94      0.94      0.93       193\n",
      "\n",
      "Accuracy from classification report: 0.9378\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "from sklearn.metrics import confusion_matrix, classification_report\n",
    "\n",
    "# Ensure the generator is reset before predictions\n",
    "validation_generator.reset()\n",
    "\n",
    "# Predict using the generator\n",
    "predictions = model.predict_generator(validation_generator, steps=len(validation_generator), verbose=1)\n",
    "\n",
    "# Convert probabilities to class predictions\n",
    "y_pred = np.argmax(predictions, axis=1)\n",
    "\n",
    "# True classes from the generator\n",
    "true_classes = validation_generator.classes\n",
    "\n",
    "# Class labels\n",
    "class_labels = list(validation_generator.class_indices.keys())\n",
    "\n",
    "# Confusion Matrix\n",
    "print(\"Confusion Matrix\")\n",
    "conf_matrix = confusion_matrix(true_classes, y_pred)\n",
    "print(conf_matrix)\n",
    "\n",
    "# Classification Report\n",
    "print(\"Classification Report\")\n",
    "report = classification_report(true_classes, y_pred, target_names=class_labels)\n",
    "print(report)\n",
    "\n",
    "# Optional: Compare accuracies\n",
    "accuracy_from_report = np.trace(conf_matrix) / np.sum(conf_matrix)\n",
    "print(f\"Accuracy from classification report: {accuracy_from_report:.4f}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d315c70b-cd18-4654-9763-5e5c719087d3",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.metrics import roc_auc_score\n",
    "from sklearn.preprocessing import label_binarize\n",
    "\n",
    "# Step 1: Binarize the true labels\n",
    "val_trues_bin = label_binarize(val_trues, classes=[0, 1, 2])  # assuming classes are 0, 1, 2\n",
    "\n",
    "# Step 2: Calculate ROC-AUC\n",
    "roc_auc = roc_auc_score(val_trues_bin, predictions, average='macro', multi_class='ovr')\n",
    "\n",
    "print(f'Multi-class ROC-AUC: {roc_auc:.4f}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c385966b-741f-498d-b601-95bbdc43219e",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn.metrics import roc_curve, auc\n",
    "from sklearn.preprocessing import label_binarize\n",
    "from sklearn.metrics import roc_auc_score\n",
    "\n",
    "# Step 1: Binarize the true labels\n",
    "n_classes = predictions.shape[1]  # should be 3\n",
    "val_trues_bin = label_binarize(val_trues, classes=[0, 1, 2])  # shape: (n_samples, n_classes)\n",
    "\n",
    "# Step 2: Compute FPR, TPR, and ROC AUC for each class\n",
    "fpr = dict()\n",
    "tpr = dict()\n",
    "roc_auc = dict()\n",
    "for i in range(n_classes):\n",
    "    fpr[i], tpr[i], _ = roc_curve(val_trues_bin[:, i], predictions[:, i])\n",
    "    roc_auc[i] = auc(fpr[i], tpr[i])\n",
    "\n",
    "# Step 3: Compute macro-average ROC curve\n",
    "# First aggregate all false positive rates\n",
    "all_fpr = np.unique(np.concatenate([fpr[i] for i in range(n_classes)]))\n",
    "\n",
    "# Then interpolate all ROC curves at these points\n",
    "mean_tpr = np.zeros_like(all_fpr)\n",
    "for i in range(n_classes):\n",
    "    mean_tpr += np.interp(all_fpr, fpr[i], tpr[i])\n",
    "\n",
    "# Average it and compute AUC\n",
    "mean_tpr /= n_classes\n",
    "fpr[\"macro\"] = all_fpr\n",
    "tpr[\"macro\"] = mean_tpr\n",
    "roc_auc[\"macro\"] = auc(fpr[\"macro\"], tpr[\"macro\"])\n",
    "\n",
    "# Step 4: Plot\n",
    "plt.figure(figsize=(8, 6))\n",
    "colors = ['blue', 'green', 'red']\n",
    "for i in range(n_classes):\n",
    "    plt.plot(fpr[i], tpr[i], color=colors[i], lw=2,\n",
    "             label=f'Class {i} (AUC = {roc_auc[i]:.2f})')\n",
    "\n",
    "plt.plot(fpr[\"macro\"], tpr[\"macro\"], color='darkorange', linestyle='--',\n",
    "         label=f'Macro-average (AUC = {roc_auc[\"macro\"]:.2f})', lw=2)\n",
    "\n",
    "plt.plot([0, 1], [0, 1], 'k--', lw=1)\n",
    "plt.xlim([0.0, 1.0])\n",
    "plt.ylim([0.0, 1.05])\n",
    "plt.xlabel('False Positive Rate')\n",
    "plt.ylabel('True Positive Rate')\n",
    "plt.title('Multi-Class ROC Curve')\n",
    "plt.legend(loc='lower right')\n",
    "plt.grid()\n",
    "plt.tight_layout()\n",
    "\n",
    "# 💾 Save the figure before displaying\n",
    "#plt.savefig(\"roc_curve.png\", dpi=300)  # You can change the filename and format if needed\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "db8d3c14",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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3HjjjZx77rls3bqVXbt2ZfciJcGE3jCMjOna1YVr/NKzyfnnn09BQQEA69ev55JLLuGTTz5BRNixY4dvmTPOOINmzZrRrFkzOnbsyBdffEGXLl2q5enbt+/utKKiIpYsWULLli058MADd/dTHzZsGGPHjq1R/44dO7jyyiuZNWsWBQUFfPzxxwBMmTKFESNGUFhYCEDbtm3ZuHEjK1eu5NxzzwXcoKe6xEI3hmFkTGkpeHq2m8JCl55NWrRosfv7LbfcwsCBA5k7dy6TJ08O7FPerFmz3d8LCgqorKwMlSfsGh333HMPnTp1Yvbs2VRUVOx+WayqNbpA5nvdDxN6wzAypqQExo6Fbt1AxH2OHZvZi9iwrF+/ns6dOwMwfvz4rNffs2dPPv30U5YsWQLAE088EWjHfvvtR6NGjZg4cSI7d+4E4JRTTuGRRx5hs/fyYu3atey999506dKF5557DoBt27btPl4XmNAbhlErSkpgyRLYtct95lLkAa6//npuuukmjj/++N3imk322msvHnjgAQYPHswJJ5xAp06daN26dY18l19+OY8++ijHHnssH3/88e6njsGDB3PWWWdRXFxMUVERd999NwATJ07kvvvu48gjj6R///58/vnnWbc9CFtK0DCMaixYsIDDDjss32bklW+++YaWLVuiqlxxxRUcfPDBXHPNNfk2azd+v5EtJWgYhpEGf/vb3ygqKuLwww9n/fr1/PSnP823SbXCet0YhmEkcM0119QrD762mEdvGIYRcUzoDcMwIo4JvWEYRsQxoTcMw4g4JvSGYdQbBgwYwCuvvFIt7d577+Xyyy9PWibWHfv0009n3bp1NfLcdtttu/uzB/Hcc88xf/783fu//vWvmTJlShrW119M6A3DqDcMGzaMSZMmVUubNGlS4MRiibz44ou0adMmo3MnCv1vf/tbBg0alFFd9Q3rXmkYRiBXXw3e9PBZo6gI7r3X/9iQIUO4+eab2bZtG82aNWPJkiWsWrWKE044gcsuu4wZM2awZcsWhgwZwm9+85sa5bt3705FRQXt27entLSUCRMmcMABB9ChQweOPvpowPWRHzt2LNu3b+eggw5i4sSJzJo1i+eff54333yTO+64g6effprbb7+dM888kyFDhjB16lSuvfZaKisrOeaYY3jwwQdp1qwZ3bt355JLLmHy5Mns2LGD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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "#PLOT\n",
    "acc = history.history['accuracy']\n",
    "val_acc = history.history['val_accuracy']\n",
    "loss = history.history['loss']\n",
    "val_loss = history.history['val_loss']\n",
    "\n",
    "epochs = range(len(acc))\n",
    "\n",
    "plt.plot(epochs, acc, 'bo', label='Training acc')\n",
    "plt.plot(epochs, val_acc, 'b', label='Validation acc')\n",
    "plt.title('Training and validation accuracy')\n",
    "plt.legend()\n",
    "\n",
    "plt.figure()\n",
    "\n",
    "plt.plot(epochs, loss, 'bo', label='Training loss')\n",
    "plt.plot(epochs, val_loss, 'b', label='Validation loss')\n",
    "plt.title('Training and validation loss')\n",
    "plt.legend()\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ddfd3320",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "471343ea",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "53b13373",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6fbcd54d",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "40b275f2",
   "metadata": {},
   "outputs": [],
   "source": [
    "#STAGE 3: With cross-validation and non color-augmentation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d7b60f65",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "from sklearn.model_selection import StratifiedKFold\n",
    "from sklearn.utils import class_weight\n",
    "from tensorflow.keras.preprocessing.image import ImageDataGenerator\n",
    "from tensorflow.keras.applications.vgg16 import preprocess_input\n",
    "from tensorflow.keras.applications import VGG16\n",
    "from tensorflow.keras.models import Model\n",
    "from tensorflow.keras.layers import Dense, Flatten, Dropout\n",
    "from tensorflow.keras.optimizers import SGD\n",
    "from sklearn.metrics import classification_report\n",
    "\n",
    "# Define paths to the directories\n",
    "original_dir2 = os.path.abspath('E:\\\\experimentos\\\\2025\\\\Dataset_2\\\\kfold')\n",
    "train_tm_dir = os.path.join(original_dir2, 'LS')\n",
    "train_cm_dir = os.path.join(original_dir2, 'SF')\n",
    "train_tmp_dir = os.path.join(original_dir2, 'tmp')\n",
    "\n",
    "# Collect all image paths and corresponding labels\n",
    "all_image_paths = []\n",
    "all_labels = []\n",
    "\n",
    "classes = [('LS', train_tm_dir), ('SF', train_cm_dir), ('tmp', train_tmp_dir)]\n",
    "\n",
    "for label, (class_name, class_dir) in enumerate(classes):\n",
    "    for image_file in os.listdir(class_dir):\n",
    "        all_image_paths.append(os.path.join(class_dir, image_file))\n",
    "        all_labels.append(label)\n",
    "\n",
    "# Convert to NumPy arrays\n",
    "all_image_paths = np.array(all_image_paths)\n",
    "all_labels = np.array(all_labels)\n",
    "\n",
    "# K-Fold Cross-Validation (Stratified)\n",
    "skf = StratifiedKFold(n_splits=4, shuffle=True, random_state=42)\n",
    "\n",
    "# Data generator with preprocessing\n",
    "datagen = ImageDataGenerator(preprocessing_function=preprocess_input)\n",
    "\n",
    "# Define the model\n",
    "def define_model():\n",
    "    # Load VGG16 without the top layer\n",
    "    base_model = VGG16(include_top=False, input_shape=(80, 400, 3))\n",
    "\n",
    "    # Freeze the base model layers\n",
    "    for layer in base_model.layers:\n",
    "        layer.trainable = False\n",
    "\n",
    "    # Add custom layers for classification\n",
    "    flat1 = Flatten()(base_model.output)\n",
    "    class1 = Dense(128, activation='swish', kernel_initializer='he_uniform')(flat1)\n",
    "    drop = Dropout(0.3)(class1)\n",
    "    output = Dense(3, activation='softmax')(drop)  # 3 classes\n",
    "\n",
    "    # Define the new model\n",
    "    model = Model(inputs=base_model.input, outputs=output)\n",
    "\n",
    "    # Compile the model with SGD optimizer\n",
    "    opt = Adagrad(learning_rate=0.001)\n",
    "    model.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['accuracy'])\n",
    "\n",
    "    return model\n",
    "\n",
    "# Initialize lists to store results\n",
    "fold_accuracies = []\n",
    "all_true_labels = []\n",
    "all_pred_labels = []\n",
    "all_cm = []\n",
    "\n",
    "# Loop over each fold\n",
    "for fold, (train_idx, val_idx) in enumerate(skf.split(all_image_paths, all_labels)):\n",
    "    print(f\"Training Fold {fold + 1}/4...\")\n",
    "\n",
    "    # Split data into training and validation sets\n",
    "    train_images = all_image_paths[train_idx]\n",
    "    train_labels = all_labels[train_idx]\n",
    "    val_images = all_image_paths[val_idx]\n",
    "    val_labels = all_labels[val_idx]\n",
    "\n",
    "    # Calculate class weights\n",
    "    class_weights = class_weight.compute_class_weight('balanced', classes=np.unique(all_labels), y=train_labels)\n",
    "    class_weight_dict = dict(enumerate(class_weights))\n",
    "\n",
    "    # Convert labels to strings (for compatibility with flow_from_dataframe)\n",
    "    train_labels_str = train_labels.astype(str)\n",
    "    val_labels_str = val_labels.astype(str)\n",
    "\n",
    "    # Create training generator\n",
    "    train_generator = datagen.flow_from_dataframe(\n",
    "        dataframe=pd.DataFrame({'filename': train_images, 'class': train_labels_str}),\n",
    "        x_col='filename',\n",
    "        y_col='class',\n",
    "        target_size=(80, 400),\n",
    "        batch_size=32,\n",
    "        class_mode='categorical'\n",
    "    )\n",
    "\n",
    "    # Create validation generator\n",
    "    validation_generator = datagen.flow_from_dataframe(\n",
    "        dataframe=pd.DataFrame({'filename': val_images, 'class': val_labels_str}),\n",
    "        x_col='filename',\n",
    "        y_col='class',\n",
    "        target_size=(80, 400),\n",
    "        batch_size=32,\n",
    "        class_mode='categorical',\n",
    "        shuffle=False\n",
    "    )\n",
    "\n",
    "    # Define the model for this fold\n",
    "    model = define_model()\n",
    "\n",
    "    # Calculate steps per epoch\n",
    "    steps_per_epoch = np.ceil(len(train_images) / 32)\n",
    "    validation_steps = np.ceil(len(val_images) / 32)\n",
    "\n",
    "    # Train the model\n",
    "    history = model.fit(\n",
    "        train_generator,\n",
    "        steps_per_epoch=steps_per_epoch,\n",
    "        epochs=10,\n",
    "        validation_data=validation_generator,\n",
    "        validation_steps=validation_steps,\n",
    "        class_weight=class_weight_dict,\n",
    "        verbose=1\n",
    "    )\n",
    "\n",
    "    # Evaluate the model\n",
    "    val_loss, val_accuracy = model.evaluate(validation_generator, verbose=0)\n",
    "    fold_accuracies.append(val_accuracy)\n",
    "    print(f\"Fold {fold + 1} accuracy: {val_accuracy:.4f}\")\n",
    "\n",
    "    # Get true and predicted labels\n",
    "    val_labels_true = val_labels.astype(int)\n",
    "    val_preds = model.predict(validation_generator)\n",
    "    val_labels_pred = np.argmax(val_preds, axis=1)\n",
    "\n",
    "    all_true_labels.extend(val_labels_true)\n",
    "    all_pred_labels.extend(val_labels_pred)\n",
    "    \n",
    "     # Compute confusion matrix for this fold\n",
    "    cm = confusion_matrix(val_labels_true, val_labels_pred)\n",
    "    all_cm.append(cm)  # Store the confusion matrix for this fold\n",
    "\n",
    "    # Print classification report for this fold\n",
    "    print(f\"Classification Report for Fold {fold + 1}\")\n",
    "    print(classification_report(val_labels_true, val_labels_pred, target_names=['LS', 'SF', 'tmp']))\n",
    "\n",
    "# Average performance across folds\n",
    "print(f\"Average accuracy across 4 folds: {np.mean(fold_accuracies):.4f}\")\n",
    "\n",
    "# Overall classification report across all folds\n",
    "print(\"Overall Classification Report\")\n",
    "print(classification_report(all_true_labels, all_pred_labels, target_names=['LS', 'SF', 'tmp']))\n",
    "\n",
    "# Optionally, print the confusion matrices for all folds\n",
    "print(\"\\nConfusion Matrices for Each Fold:\")\n",
    "for i, cm in enumerate(all_cm):\n",
    "    print(f\"Fold {i + 1}:\")\n",
    "    print(cm)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bb15f3ba",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Compute the overall confusion matrix (combining all folds)\n",
    "overall_cm = confusion_matrix(all_true_labels, all_pred_labels)\n",
    "\n",
    "# Print the overall confusion matrix\n",
    "print(\"\\nOverall Confusion Matrix:\")\n",
    "print(overall_cm)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5f77d3a8",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 101,
   "id": "3c987518",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Training Fold 1/4...\n",
      "Found 491 validated image filenames belonging to 3 classes.\n",
      "Found 164 validated image filenames belonging to 3 classes.\n",
      "Epoch 1/10\n",
      "16/16 [==============================] - 20s 1s/step - loss: 1.8255 - accuracy: 0.7637 - val_loss: 0.4949 - val_accuracy: 0.8537\n",
      "Epoch 2/10\n",
      "16/16 [==============================] - 20s 1s/step - loss: 0.1930 - accuracy: 0.9369 - val_loss: 0.3967 - val_accuracy: 0.9085\n",
      "Epoch 3/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0813 - accuracy: 0.9735 - val_loss: 0.3719 - val_accuracy: 0.9146\n",
      "Epoch 4/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0787 - accuracy: 0.9735 - val_loss: 0.4268 - val_accuracy: 0.9329\n",
      "Epoch 5/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0453 - accuracy: 0.9857 - val_loss: 0.3699 - val_accuracy: 0.9207\n",
      "Epoch 6/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0218 - accuracy: 0.9919 - val_loss: 0.3605 - val_accuracy: 0.9207\n",
      "Epoch 7/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0315 - accuracy: 0.9878 - val_loss: 0.3558 - val_accuracy: 0.9390\n",
      "Epoch 8/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0173 - accuracy: 0.9919 - val_loss: 0.3537 - val_accuracy: 0.9329\n",
      "Epoch 9/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0164 - accuracy: 0.9959 - val_loss: 0.3680 - val_accuracy: 0.9329\n",
      "Epoch 10/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0105 - accuracy: 0.9939 - val_loss: 0.3629 - val_accuracy: 0.9329\n",
      "Fold 1 accuracy: 0.9329\n",
      "6/6 [==============================] - 5s 835ms/step\n",
      "Classification Report for Fold 1\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "          LS       0.77      0.92      0.84        26\n",
      "          SF       0.98      0.98      0.98       122\n",
      "         tmp       0.83      0.62      0.71        16\n",
      "\n",
      "    accuracy                           0.93       164\n",
      "   macro avg       0.86      0.84      0.85       164\n",
      "weighted avg       0.94      0.93      0.93       164\n",
      "\n",
      "Training Fold 2/4...\n",
      "Found 491 validated image filenames belonging to 3 classes.\n",
      "Found 164 validated image filenames belonging to 3 classes.\n",
      "Epoch 1/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 1.6927 - accuracy: 0.7210 - val_loss: 0.4526 - val_accuracy: 0.9085\n",
      "Epoch 2/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.2064 - accuracy: 0.9267 - val_loss: 0.3146 - val_accuracy: 0.9268\n",
      "Epoch 3/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0985 - accuracy: 0.9552 - val_loss: 0.3039 - val_accuracy: 0.9268\n",
      "Epoch 4/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.1053 - accuracy: 0.9674 - val_loss: 0.3348 - val_accuracy: 0.9085\n",
      "Epoch 5/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.1072 - accuracy: 0.9613 - val_loss: 0.3161 - val_accuracy: 0.9207\n",
      "Epoch 6/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0528 - accuracy: 0.9695 - val_loss: 0.3155 - val_accuracy: 0.9268\n",
      "Epoch 7/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0308 - accuracy: 0.9837 - val_loss: 0.2837 - val_accuracy: 0.9268\n",
      "Epoch 8/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0605 - accuracy: 0.9857 - val_loss: 0.2587 - val_accuracy: 0.9329\n",
      "Epoch 9/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0158 - accuracy: 0.9939 - val_loss: 0.2651 - val_accuracy: 0.9268\n",
      "Epoch 10/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0117 - accuracy: 1.0000 - val_loss: 0.2654 - val_accuracy: 0.9268\n",
      "Fold 2 accuracy: 0.9268\n",
      "6/6 [==============================] - 5s 815ms/step\n",
      "Classification Report for Fold 2\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "          LS       0.91      0.77      0.83        26\n",
      "          SF       0.95      0.98      0.96       122\n",
      "         tmp       0.76      0.81      0.79        16\n",
      "\n",
      "    accuracy                           0.93       164\n",
      "   macro avg       0.88      0.85      0.86       164\n",
      "weighted avg       0.93      0.93      0.93       164\n",
      "\n",
      "Training Fold 3/4...\n",
      "Found 491 validated image filenames belonging to 3 classes.\n",
      "Found 164 validated image filenames belonging to 3 classes.\n",
      "Epoch 1/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 1.7097 - accuracy: 0.7617 - val_loss: 0.5456 - val_accuracy: 0.8293\n",
      "Epoch 2/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.3862 - accuracy: 0.8737 - val_loss: 0.2410 - val_accuracy: 0.8780\n",
      "Epoch 3/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.1120 - accuracy: 0.9572 - val_loss: 0.2477 - val_accuracy: 0.9207\n",
      "Epoch 4/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0588 - accuracy: 0.9715 - val_loss: 0.1490 - val_accuracy: 0.9451\n",
      "Epoch 5/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0630 - accuracy: 0.9837 - val_loss: 0.1422 - val_accuracy: 0.9512\n",
      "Epoch 6/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0553 - accuracy: 0.9796 - val_loss: 0.1383 - val_accuracy: 0.9512\n",
      "Epoch 7/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0224 - accuracy: 0.9919 - val_loss: 0.1445 - val_accuracy: 0.9512\n",
      "Epoch 8/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0188 - accuracy: 0.9939 - val_loss: 0.1452 - val_accuracy: 0.9451\n",
      "Epoch 9/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0397 - accuracy: 0.9898 - val_loss: 0.1244 - val_accuracy: 0.9634\n",
      "Epoch 10/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0168 - accuracy: 0.9980 - val_loss: 0.1202 - val_accuracy: 0.9573\n",
      "Fold 3 accuracy: 0.9573\n",
      "6/6 [==============================] - 5s 819ms/step\n",
      "Classification Report for Fold 3\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "          LS       0.93      0.96      0.94        26\n",
      "          SF       0.97      0.99      0.98       122\n",
      "         tmp       0.92      0.69      0.79        16\n",
      "\n",
      "    accuracy                           0.96       164\n",
      "   macro avg       0.94      0.88      0.90       164\n",
      "weighted avg       0.96      0.96      0.96       164\n",
      "\n",
      "Training Fold 4/4...\n",
      "Found 492 validated image filenames belonging to 3 classes.\n",
      "Found 163 validated image filenames belonging to 3 classes.\n",
      "Epoch 1/10\n",
      "16/16 [==============================] - 22s 1s/step - loss: 1.9439 - accuracy: 0.7195 - val_loss: 0.4093 - val_accuracy: 0.8650\n",
      "Epoch 2/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.1905 - accuracy: 0.9309 - val_loss: 0.3156 - val_accuracy: 0.9080\n",
      "Epoch 3/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.1036 - accuracy: 0.9695 - val_loss: 0.2682 - val_accuracy: 0.9387\n",
      "Epoch 4/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0774 - accuracy: 0.9736 - val_loss: 0.2662 - val_accuracy: 0.9202\n",
      "Epoch 5/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0587 - accuracy: 0.9837 - val_loss: 0.2630 - val_accuracy: 0.9325\n",
      "Epoch 6/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0296 - accuracy: 0.9898 - val_loss: 0.2524 - val_accuracy: 0.9387\n",
      "Epoch 7/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0213 - accuracy: 0.9898 - val_loss: 0.2566 - val_accuracy: 0.9448\n",
      "Epoch 8/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0233 - accuracy: 0.9939 - val_loss: 0.2538 - val_accuracy: 0.9448\n",
      "Epoch 9/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0229 - accuracy: 0.9919 - val_loss: 0.2477 - val_accuracy: 0.9387\n",
      "Epoch 10/10\n",
      "16/16 [==============================] - 21s 1s/step - loss: 0.0141 - accuracy: 0.9939 - val_loss: 0.2453 - val_accuracy: 0.9448\n",
      "Fold 4 accuracy: 0.9448\n",
      "6/6 [==============================] - 5s 811ms/step\n",
      "Classification Report for Fold 4\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "          LS       0.88      0.88      0.88        25\n",
      "          SF       0.98      0.99      0.98       123\n",
      "         tmp       0.77      0.67      0.71        15\n",
      "\n",
      "    accuracy                           0.94       163\n",
      "   macro avg       0.88      0.85      0.86       163\n",
      "weighted avg       0.94      0.94      0.94       163\n",
      "\n",
      "Average accuracy across 4 folds: 0.9405\n",
      "Overall Classification Report\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "          LS       0.87      0.88      0.88       103\n",
      "          SF       0.97      0.98      0.98       489\n",
      "         tmp       0.81      0.70      0.75        63\n",
      "\n",
      "    accuracy                           0.94       655\n",
      "   macro avg       0.88      0.86      0.87       655\n",
      "weighted avg       0.94      0.94      0.94       655\n",
      "\n",
      "\n",
      "Confusion Matrices for Each Fold:\n",
      "Fold 1:\n",
      "[[ 24   0   2]\n",
      " [  3 119   0]\n",
      " [  4   2  10]]\n",
      "Fold 2:\n",
      "[[ 20   4   2]\n",
      " [  1 119   2]\n",
      " [  1   2  13]]\n",
      "Fold 3:\n",
      "[[ 25   1   0]\n",
      " [  0 121   1]\n",
      " [  2   3  11]]\n",
      "Fold 4:\n",
      "[[ 22   1   2]\n",
      " [  0 122   1]\n",
      " [  3   2  10]]\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "from sklearn.model_selection import StratifiedKFold\n",
    "from sklearn.utils import class_weight\n",
    "from tensorflow.keras.preprocessing.image import ImageDataGenerator\n",
    "from tensorflow.keras.applications.vgg16 import preprocess_input\n",
    "from tensorflow.keras.applications import VGG16\n",
    "from tensorflow.keras.models import Model\n",
    "from tensorflow.keras.layers import Dense, Flatten, Dropout\n",
    "from tensorflow.keras.optimizers import SGD\n",
    "from sklearn.metrics import classification_report\n",
    "\n",
    "# Define paths to the directories\n",
    "original_dir2 = os.path.abspath('C:\\\\Users\\\\oscar\\\\Documents\\\\experimentos\\\\Dataset2\\\\kfold')\n",
    "train_tm_dir = os.path.join(original_dir2, 'LS')\n",
    "train_cm_dir = os.path.join(original_dir2, 'SF')\n",
    "train_tmp_dir = os.path.join(original_dir2, 'tmp')\n",
    "\n",
    "# Collect all image paths and corresponding labels\n",
    "all_image_paths = []\n",
    "all_labels = []\n",
    "\n",
    "classes = [('LS', train_tm_dir), ('SF', train_cm_dir), ('tmp', train_tmp_dir)]\n",
    "\n",
    "for label, (class_name, class_dir) in enumerate(classes):\n",
    "    for image_file in os.listdir(class_dir):\n",
    "        all_image_paths.append(os.path.join(class_dir, image_file))\n",
    "        all_labels.append(label)\n",
    "\n",
    "# Convert to NumPy arrays\n",
    "all_image_paths = np.array(all_image_paths)\n",
    "all_labels = np.array(all_labels)\n",
    "\n",
    "# K-Fold Cross-Validation (Stratified)\n",
    "skf = StratifiedKFold(n_splits=4, shuffle=True, random_state=42)\n",
    "\n",
    "# Data generator with preprocessing\n",
    "datagen = ImageDataGenerator(preprocessing_function=preprocess_input)\n",
    "\n",
    "# Define the model\n",
    "def define_model():\n",
    "    # Load VGG16 without the top layer\n",
    "    base_model = VGG16(include_top=False, input_shape=(80, 400, 3))\n",
    "\n",
    "    # Freeze the base model layers\n",
    "    for layer in base_model.layers:\n",
    "        layer.trainable = False\n",
    "\n",
    "    # Add custom layers for classification\n",
    "    flat1 = Flatten()(base_model.output)\n",
    "    class1 = Dense(128, activation='swish', kernel_initializer='he_uniform')(flat1)\n",
    "    drop = Dropout(0.3)(class1)\n",
    "    output = Dense(3, activation='softmax')(drop)  # 3 classes\n",
    "\n",
    "    # Define the new model\n",
    "    model = Model(inputs=base_model.input, outputs=output)\n",
    "\n",
    "    # Compile the model with SGD optimizer\n",
    "    opt = Adagrad(learning_rate=0.001)\n",
    "    model.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['accuracy'])\n",
    "\n",
    "    return model\n",
    "\n",
    "# Initialize lists to store results\n",
    "fold_accuracies = []\n",
    "all_true_labels = []\n",
    "all_pred_labels = []\n",
    "all_cm = []\n",
    "\n",
    "# Loop over each fold\n",
    "for fold, (train_idx, val_idx) in enumerate(skf.split(all_image_paths, all_labels)):\n",
    "    print(f\"Training Fold {fold + 1}/4...\")\n",
    "\n",
    "    # Split data into training and validation sets\n",
    "    train_images = all_image_paths[train_idx]\n",
    "    train_labels = all_labels[train_idx]\n",
    "    val_images = all_image_paths[val_idx]\n",
    "    val_labels = all_labels[val_idx]\n",
    "\n",
    "    # Calculate class weights\n",
    "    class_weights = class_weight.compute_class_weight('balanced', classes=np.unique(all_labels), y=train_labels)\n",
    "    class_weight_dict = dict(enumerate(class_weights))\n",
    "\n",
    "    # Convert labels to strings (for compatibility with flow_from_dataframe)\n",
    "    train_labels_str = train_labels.astype(str)\n",
    "    val_labels_str = val_labels.astype(str)\n",
    "\n",
    "    # Create training generator\n",
    "    train_generator = datagen.flow_from_dataframe(\n",
    "        dataframe=pd.DataFrame({'filename': train_images, 'class': train_labels_str}),\n",
    "        x_col='filename',\n",
    "        y_col='class',\n",
    "        target_size=(80, 400),\n",
    "        batch_size=32,\n",
    "        class_mode='categorical'\n",
    "    )\n",
    "\n",
    "    # Create validation generator\n",
    "    validation_generator = datagen.flow_from_dataframe(\n",
    "        dataframe=pd.DataFrame({'filename': val_images, 'class': val_labels_str}),\n",
    "        x_col='filename',\n",
    "        y_col='class',\n",
    "        target_size=(80, 400),\n",
    "        batch_size=32,\n",
    "        class_mode='categorical',\n",
    "        shuffle=False\n",
    "    )\n",
    "\n",
    "    # Define the model for this fold\n",
    "    model = define_model()\n",
    "\n",
    "    # Calculate steps per epoch\n",
    "    steps_per_epoch = np.ceil(len(train_images) / 32)\n",
    "    validation_steps = np.ceil(len(val_images) / 32)\n",
    "\n",
    "    # Train the model\n",
    "    history = model.fit(\n",
    "        train_generator,\n",
    "        steps_per_epoch=steps_per_epoch,\n",
    "        epochs=10,\n",
    "        validation_data=validation_generator,\n",
    "        validation_steps=validation_steps,\n",
    "        class_weight=class_weight_dict,\n",
    "        verbose=1\n",
    "    )\n",
    "\n",
    "    # Evaluate the model\n",
    "    val_loss, val_accuracy = model.evaluate(validation_generator, verbose=0)\n",
    "    fold_accuracies.append(val_accuracy)\n",
    "    print(f\"Fold {fold + 1} accuracy: {val_accuracy:.4f}\")\n",
    "\n",
    "    # Get true and predicted labels\n",
    "    val_labels_true = val_labels.astype(int)\n",
    "    val_preds = model.predict(validation_generator)\n",
    "    val_labels_pred = np.argmax(val_preds, axis=1)\n",
    "\n",
    "    all_true_labels.extend(val_labels_true)\n",
    "    all_pred_labels.extend(val_labels_pred)\n",
    "    \n",
    "     # Compute confusion matrix for this fold\n",
    "    cm = confusion_matrix(val_labels_true, val_labels_pred)\n",
    "    all_cm.append(cm)  # Store the confusion matrix for this fold\n",
    "\n",
    "    # Print classification report for this fold\n",
    "    print(f\"Classification Report for Fold {fold + 1}\")\n",
    "    print(classification_report(val_labels_true, val_labels_pred, target_names=['LS', 'SF', 'tmp']))\n",
    "\n",
    "# Average performance across folds\n",
    "print(f\"Average accuracy across 4 folds: {np.mean(fold_accuracies):.4f}\")\n",
    "\n",
    "# Overall classification report across all folds\n",
    "print(\"Overall Classification Report\")\n",
    "print(classification_report(all_true_labels, all_pred_labels, target_names=['LS', 'SF', 'tmp']))\n",
    "\n",
    "# Optionally, print the confusion matrices for all folds\n",
    "print(\"\\nConfusion Matrices for Each Fold:\")\n",
    "for i, cm in enumerate(all_cm):\n",
    "    print(f\"Fold {i + 1}:\")\n",
    "    print(cm)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "121c2191",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "68193163",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f11a3658",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1536eb31",
   "metadata": {},
   "outputs": [],
   "source": [
    "#STAGE 4: FEW-SHOT SUPERVISED LEARNING"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a6096a2c",
   "metadata": {},
   "outputs": [],
   "source": [
    "import tensorflow as tf\n",
    "from tensorflow.keras import layers, models, optimizers\n",
    "from tensorflow.keras.applications import VGG16\n",
    "from tensorflow.keras.applications import resnet50\n",
    "from sklearn.model_selection import train_test_split\n",
    "import numpy as np\n",
    "import os\n",
    "from glob import glob\n",
    "from tensorflow.keras.applications.vgg16 import preprocess_input \n",
    "from tensorflow.keras.preprocessing.image import load_img, img_to_array\n",
    "\n",
    "# Set your directory containing the images\n",
    "data_directory = 'C:\\\\Users\\\\oscar\\\\Documents\\\\experimentos\\\\Dataset2\\\\kfold'\n",
    "\n",
    "# Function to load and resize images from a directory\n",
    "\n",
    "def load_images(directory, target_size=(80, 400)):\n",
    "    image_list = []\n",
    "    file_pattern = os.path.join(directory, '*.bmp')  # Modify as needed for other file types\n",
    "    for filename in glob(file_pattern):\n",
    "        print(f\"Loading, resizing, and preprocessing image: {filename}\")\n",
    "        img = load_img(filename, target_size=target_size)\n",
    "        img_array = img_to_array(img)\n",
    "        img_array = preprocess_input(img_array)  # Apply TL model preprocessing\n",
    "        image_list.append(img_array)\n",
    "    return np.array(image_list)\n",
    "\n",
    "\n",
    "# Load images for each class\n",
    "class1_images = load_images(os.path.join(data_directory, 'LS'))\n",
    "class2_images = load_images(os.path.join(data_directory, 'SF'))\n",
    "class3_images = load_images(os.path.join(data_directory, 'tmp'))\n",
    "\n",
    "# Create labels for each class\n",
    "class1_labels = np.zeros(len(class1_images))\n",
    "class2_labels = np.ones(len(class2_images))\n",
    "class3_labels = 2 * np.ones(len(class3_images))\n",
    "\n",
    "# Concatenate images and labels for all classes\n",
    "all_images = np.concatenate([class1_images, class2_images,class3_images])\n",
    "all_labels = np.concatenate([class1_labels, class2_labels, class3_labels])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 104,
   "id": "4d45818c",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "# Split the data into training (70%) and the rest (30%)\n",
    "train_images, temp_images, train_labels, temp_labels = train_test_split(\n",
    "    all_images, all_labels, test_size=0.3, random_state=42\n",
    ")\n",
    "\n",
    "# Split the remaining 30% into validation (15%) and testing (15%)\n",
    "val_images, test_images, val_labels, test_labels = train_test_split(\n",
    "    temp_images, temp_labels, test_size=0.5, random_state=42\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 105,
   "id": "24905f89",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Number of training images: 458\n",
      "Number of validation images: 98\n",
      "Number of testing images: 99\n"
     ]
    }
   ],
   "source": [
    "num_training_images = len(train_images)\n",
    "print(f'Number of training images: {num_training_images}')\n",
    "\n",
    "num_validation_images = len(val_images)\n",
    "print(f'Number of validation images: {num_validation_images}')\n",
    "\n",
    "num_testing_images = len(test_images)\n",
    "print(f'Number of testing images: {num_testing_images}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9ed720f5-dbc0-42ce-b701-8d7c31606758",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3833845d-477a-4613-9f55-ca07f07ec3d3",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 113,
   "id": "b4394bab",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Load the pre-trained VGG16 model without the top classification layers\n",
    "#from tensorflow.keras.applications import VGG16\n",
    "#base_model = VGG16(weights='imagenet', include_top=False, input_shape=(80, 400, 3))\n",
    "base_model = tf.keras.applications.VGG16(weights='imagenet', include_top=False, input_shape=(80, 400, 3))\n",
    "\n",
    "# Freeze the convolutional layers of the ResNet50 base model\n",
    "for layer in base_model.layers:\n",
    "    layer.trainable = False\n",
    "# Freeze the convolutional layers of the VGG16 base model\n",
    "for layer in base_model.layers:\n",
    "    layer.trainable = False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2960e9ea",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 116,
   "id": "5e353838",
   "metadata": {},
   "outputs": [],
   "source": [
    "from tensorflow.keras.callbacks import EarlyStopping, LearningRateScheduler\n",
    "from tensorflow.keras.preprocessing.image import ImageDataGenerator\n",
    "import tensorflow as tf\n",
    "import numpy as np\n",
    "import random\n",
    "\n",
    "# Set random seed for TensorFlow\n",
    "tf.random.set_seed(42)\n",
    "\n",
    "# Set random seed for numpy\n",
    "np.random.seed(42)\n",
    "\n",
    "# Set random seed for Python random module\n",
    "random.seed(42)\n",
    "\n",
    "def create_maml_model(base_model, num_classes):\n",
    "    maml_model = models.Sequential([\n",
    "        base_model,\n",
    "        layers.Conv2D(512, (3, 3), activation='relu', padding='same'),  # Changed to 'same' padding\n",
    "        layers.GlobalAveragePooling2D(),\n",
    "        layers.Dense(512, activation='relu'),\n",
    "        layers.Dropout(0.5),\n",
    "        layers.BatchNormalization(),\n",
    "        layers.Dense(num_classes, activation='softmax')\n",
    "    ])\n",
    "    return maml_model\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6a998f28",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "# Set up FEW-SHOT model\n",
    "num_classes = 3  # Number of output classes\n",
    "maml_model = create_maml_model(base_model, num_classes)\n",
    "\n",
    "# Set up FEW-SHOT optimizer\n",
    "meta_optimizer = optimizers.Adam(learning_rate=1e-3)\n",
    "#meta_optimizer = optimizers.Adagrad(learning_rate=0.001)\n",
    "#meta_optimizer = optimizers.SGD(learning_rate=0.001, momentum=0.9)\n",
    "\n",
    "# Compile the FEW-SHOT model\n",
    "maml_model.compile(optimizer=meta_optimizer, loss='sparse_categorical_crossentropy', metrics=['accuracy'])\n",
    "\n",
    "\n",
    "# Lists to store training, validation, and testing history\n",
    "train_loss_history = []\n",
    "train_acc_history = []\n",
    "val_loss_history = []\n",
    "val_acc_history = []\n",
    "test_loss_history = []\n",
    "test_acc_history = []\n",
    "\n",
    "# Training loop for few-shot learning\n",
    "num_epochs = 100  # Increase the number of epochs\n",
    "num_tasks = 30  # Number of few-shot tasks\n",
    "validation_interval = 1  # Evaluate on the validation set every 'validation_interval' epochs\n",
    "num_shots = 5  # Number of examples per class in each few-shot task\n",
    "\n",
    "# Early stopping parameters\n",
    "patience = 15\n",
    "best_val_loss = float('inf')\n",
    "wait = 0\n",
    "\n",
    "# Data Augmentation\n",
    "datagen = ImageDataGenerator(\n",
    "    rotation_range=20,\n",
    "    width_shift_range=0.2,\n",
    "    height_shift_range=0.2,\n",
    "    horizontal_flip=True\n",
    ")\n",
    "\n",
    "# Callbacks\n",
    "early_stopping = tf.keras.callbacks.EarlyStopping(\n",
    "    monitor='val_loss',\n",
    "    patience=patience,\n",
    "    restore_best_weights=True\n",
    ")\n",
    "# Define the directory where you want to save the best model\n",
    "save_dir = 'C:\\\\Users\\\\oscar\\\\Documents\\\\experimentos\\\\Dataset2\\\\kfold'\n",
    "\n",
    "# Callback to save the best model\n",
    "model_checkpoint = tf.keras.callbacks.ModelCheckpoint(\n",
    "    os.path.join(save_dir, 'best_Resn_model5_15.keras'),  # Full path to save the best model\n",
    "    monitor='val_loss',\n",
    "    save_best_only=True\n",
    ")\n",
    "\n",
    "\n",
    "# Learning rate scheduler function\n",
    "def lr_schedule(epoch):\n",
    "    lr = 1e-3\n",
    "    if epoch > 10:\n",
    "        lr *= 0.1\n",
    "    if epoch > 20:\n",
    "        lr *= 0.1\n",
    "    if epoch > 30:\n",
    "        lr *= 0.1\n",
    "    return lr\n",
    "\n",
    "# Training loop\n",
    "for epoch in range(num_epochs):\n",
    "    for task in range(num_tasks):\n",
    "        task_samples = []\n",
    "        task_labels = []\n",
    "        for class_idx in range(num_classes):\n",
    "            class_indices = np.where(train_labels == class_idx)[0]\n",
    "            selected_indices = np.random.choice(class_indices, num_shots, replace=False)\n",
    "            selected_samples = train_images[selected_indices]\n",
    "            task_samples.extend(selected_samples)\n",
    "            task_labels.extend([class_idx] * num_shots)\n",
    "\n",
    "        task_samples = np.array(task_samples)\n",
    "        task_labels = np.array(task_labels)\n",
    "\n",
    "        # Fine-tune the model on the few-shot task with data augmentation\n",
    "        with tf.GradientTape() as tape:\n",
    "            augmented_task_samples = []\n",
    "            for sample in task_samples:\n",
    "                augmented_sample = datagen.random_transform(sample)\n",
    "                augmented_task_samples.append(augmented_sample)\n",
    "            augmented_task_samples = np.array(augmented_task_samples)\n",
    "\n",
    "            logits = maml_model(augmented_task_samples)\n",
    "            loss = tf.losses.sparse_categorical_crossentropy(task_labels, logits)\n",
    "\n",
    "        gradients = tape.gradient(loss, maml_model.trainable_variables)\n",
    "        meta_optimizer.apply_gradients(zip(gradients, maml_model.trainable_variables))\n",
    "\n",
    "    train_loss, train_acc = maml_model.evaluate(train_images, train_labels, verbose=0)\n",
    "    print(f'Epoch {epoch + 1}/{num_epochs} - Train Loss: {train_loss:.4f}, Train Accuracy: {train_acc * 100:.2f}%')\n",
    "\n",
    "    train_loss_history.append(train_loss)\n",
    "    train_acc_history.append(train_acc)\n",
    "\n",
    "    if epoch % validation_interval == 0:\n",
    "        val_loss, val_acc = maml_model.evaluate(val_images, val_labels, verbose=0)\n",
    "        print(f'Epoch {epoch + 1}/{num_epochs} - Validation Loss: {val_loss:.4f}, Validation Accuracy: {val_acc * 100:.2f}%')\n",
    "        \n",
    "        val_loss_history.append(val_loss)\n",
    "        val_acc_history.append(val_acc)\n",
    "\n",
    "        if val_loss < best_val_loss:\n",
    "            best_val_loss = val_loss\n",
    "            wait = 0\n",
    "            # Save the best model\n",
    "            maml_model.save(os.path.join(save_dir, 'best_model.h5'))#change model name\n",
    "        else:\n",
    "            wait += 1\n",
    "            if wait >= patience:\n",
    "                print(f'Early stopping at epoch {epoch + 1}')\n",
    "                break\n",
    "\n",
    "        # Apply learning rate scheduler\n",
    "    lr = lr_schedule(epoch)\n",
    "    tf.keras.backend.set_value(meta_optimizer.lr, lr)\n",
    "    print(f'Learning Rate: {lr}')\n",
    "\n",
    "# After training, you can use the test set for the final evaluation\n",
    "test_loss, test_acc = maml_model.evaluate(test_images, test_labels, verbose=0)\n",
    "print(f'Final Testing Loss: {test_loss:.4f}, Testing Accuracy: {test_acc * 100:.2f}%')\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d3c53c27",
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "# Plotting the training and validation history\n",
    "plt.figure(figsize=(12, 4))\n",
    "plt.subplot(1, 2, 1)\n",
    "plt.plot(train_loss_history, label='Train Loss')\n",
    "plt.plot(val_loss_history, label='Validation Loss')\n",
    "plt.title('Training and Validation Loss')\n",
    "plt.xlabel('Epochs')\n",
    "plt.ylabel('Loss')\n",
    "plt.legend()\n",
    "\n",
    "plt.subplot(1, 2, 2)\n",
    "plt.plot(train_acc_history, label='Train Accuracy')\n",
    "plt.plot(val_acc_history, label='Validation Accuracy')\n",
    "plt.title('Training and Validation Accuracy')\n",
    "plt.xlabel('Epochs')\n",
    "plt.ylabel('Accuracy')\n",
    "plt.legend()\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "aafe74d0",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.metrics import classification_report\n",
    "\n",
    "# After training, you can use the test set for the final evaluation\n",
    "test_loss, test_acc = maml_model.evaluate(test_images, test_labels, verbose=0)\n",
    "print(f'Final Testing Loss: {test_loss:.4f}, Testing Accuracy: {test_acc * 100:.2f}%')\n",
    "\n",
    "# Predict probabilities for test images\n",
    "y_pred_prob = maml_model.predict(test_images)\n",
    "\n",
    "# Convert probabilities to class labels\n",
    "y_pred = np.argmax(y_pred_prob, axis=1)\n",
    "\n",
    "# Generate classification report\n",
    "print(classification_report(test_labels, y_pred))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2033b374",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "119cfbf2-de30-4ccd-bae8-1308956d3850",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c4717881-2233-48aa-ae9b-4696286eddb8",
   "metadata": {},
   "outputs": [],
   "source": [
    "#STAGE 5. MAML"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3887e9b1-88d2-45be-b1cb-aa2e74d23d08",
   "metadata": {},
   "outputs": [],
   "source": [
    "#MAML\n",
    "import os\n",
    "import numpy as np\n",
    "import random\n",
    "import tensorflow as tf\n",
    "from tensorflow.keras import layers, models, optimizers\n",
    "from tensorflow.keras.preprocessing.image import ImageDataGenerator\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# Set random seeds for reproducibility\n",
    "tf.random.set_seed(42)\n",
    "np.random.seed(42)\n",
    "random.seed(42)\n",
    "\n",
    "# Define base model using VGG16\n",
    "base_model = tf.keras.applications.VGG16(weights='imagenet', include_top=False, input_shape=(80, 400, 3))\n",
    "for layer in base_model.layers:\n",
    "    layer.trainable = False\n",
    "\n",
    "# Unfreeze last few VGG16 layers for better adaptation\n",
    "for layer in base_model.layers[-4:]:\n",
    "    layer.trainable = True\n",
    "\n",
    "def create_maml_model(base_model, num_classes):\n",
    "    maml_model = models.Sequential([\n",
    "        base_model,\n",
    "        layers.Conv2D(512, (3, 3), activation='relu', padding='same'),\n",
    "        layers.GlobalAveragePooling2D(),\n",
    "        layers.Dense(512, activation='relu'),\n",
    "        layers.Dropout(0.5),\n",
    "        layers.BatchNormalization(),\n",
    "        layers.Dense(num_classes, activation='softmax')\n",
    "    ])\n",
    "    return maml_model\n",
    "\n",
    "# MAML hyperparameters\n",
    "num_classes = 3\n",
    "n_way = num_classes\n",
    "k_shot = 5\n",
    "q_query = 5\n",
    "num_tasks = 20\n",
    "num_epochs = 100\n",
    "alpha = 0.01\n",
    "beta = 0.001\n",
    "inner_steps = 5\n",
    "\n",
    "# Instantiate model and optimizer\n",
    "maml_model = create_maml_model(base_model, num_classes)\n",
    "meta_optimizer = tf.keras.optimizers.Adam(learning_rate=beta)\n",
    "\n",
    "# Paths for saving\n",
    "save_dir = 'C:\\\\Users\\\\oscar\\\\Documents\\\\experimentos\\\\Dataset2\\\\kfold'\n",
    "os.makedirs(save_dir, exist_ok=True)\n",
    "\n",
    "# Data augmentation\n",
    "datagen = ImageDataGenerator(\n",
    "    rotation_range=20,\n",
    "    width_shift_range=0.2,\n",
    "    height_shift_range=0.2,\n",
    "    horizontal_flip=True\n",
    ")\n",
    "\n",
    "def maml_task_step(model, support_images, support_labels, query_images, query_labels):\n",
    "    with tf.GradientTape() as meta_tape:\n",
    "        # Clone the weights from the model for inner loop\n",
    "        fast_weights = model.trainable_variables\n",
    "\n",
    "        # Inner loop adaptation\n",
    "        for _ in range(inner_steps):\n",
    "            with tf.GradientTape() as tape:\n",
    "                support_preds = model(support_images, training=True)\n",
    "                support_loss = tf.keras.losses.sparse_categorical_crossentropy(support_labels, support_preds)\n",
    "                support_loss = tf.reduce_mean(support_loss)\n",
    "            grads = tape.gradient(support_loss, fast_weights)\n",
    "            fast_weights = [w - alpha * g if g is not None else w for w, g in zip(fast_weights, grads)]\n",
    "\n",
    "        # Compute query predictions using the adapted weights\n",
    "        def forward_with_weights(x, weights):\n",
    "            idx = 0\n",
    "            for layer in model.layers:\n",
    "                if hasattr(layer, 'trainable_variables') and layer.trainable_variables:\n",
    "                    n_vars = len(layer.trainable_variables)\n",
    "                    for i in range(n_vars):\n",
    "                        layer.trainable_variables[i].assign(weights[idx])\n",
    "                        idx += 1\n",
    "            return model(x, training=True)\n",
    "\n",
    "        query_preds = forward_with_weights(query_images, fast_weights)\n",
    "        query_loss = tf.keras.losses.sparse_categorical_crossentropy(query_labels, query_preds)\n",
    "        query_loss = tf.reduce_mean(query_loss)\n",
    "\n",
    "    meta_grads = meta_tape.gradient(query_loss, model.trainable_variables)\n",
    "    return meta_grads, query_loss\n",
    "\n",
    "# Track loss and accuracy for plotting\n",
    "train_loss_history = []\n",
    "val_loss_history = []\n",
    "train_acc_history = []\n",
    "val_acc_history = []\n",
    "\n",
    "# MAML training loop\n",
    "best_val_loss = float('inf')\n",
    "patience = 15\n",
    "wait = 0\n",
    "\n",
    "for epoch in range(num_epochs):\n",
    "    print(f\"\\nEpoch {epoch + 1}/{num_epochs}\")\n",
    "    meta_grads_accum = [tf.zeros_like(var) for var in maml_model.trainable_variables]\n",
    "    epoch_loss = []\n",
    "\n",
    "    for task in range(num_tasks):\n",
    "        support_images, support_labels, query_images, query_labels = [], [], [], []\n",
    "\n",
    "        for class_idx in range(n_way):\n",
    "            class_indices = np.where(train_labels == class_idx)[0]\n",
    "            selected_indices = np.random.choice(class_indices, k_shot + q_query, replace=False)\n",
    "            support_idx = selected_indices[:k_shot]\n",
    "            query_idx = selected_indices[k_shot:]\n",
    "\n",
    "            support_images.extend(train_images[support_idx])\n",
    "            support_labels.extend([class_idx] * k_shot)\n",
    "            query_images.extend(train_images[query_idx])\n",
    "            query_labels.extend([class_idx] * q_query)\n",
    "\n",
    "        support_images = tf.convert_to_tensor(np.array(support_images), dtype=tf.float32)\n",
    "        support_labels = tf.convert_to_tensor(np.array(support_labels), dtype=tf.int32)\n",
    "        query_images = tf.convert_to_tensor(np.array(query_images), dtype=tf.float32)\n",
    "        query_labels = tf.convert_to_tensor(np.array(query_labels), dtype=tf.int32)\n",
    "\n",
    "        meta_grads, loss = maml_task_step(\n",
    "            maml_model, support_images, support_labels, query_images, query_labels\n",
    "        )\n",
    "\n",
    "        meta_grads_accum = [\n",
    "            acc + (g if g is not None else tf.zeros_like(acc))\n",
    "            for acc, g in zip(meta_grads_accum, meta_grads)\n",
    "        ]\n",
    "        epoch_loss.append(loss.numpy())\n",
    "\n",
    "    mean_grads = [g / num_tasks for g in meta_grads_accum]\n",
    "    meta_optimizer.apply_gradients(zip(mean_grads, maml_model.trainable_variables))\n",
    "\n",
    "    mean_query_loss = np.mean(epoch_loss)\n",
    "    train_loss_history.append(mean_query_loss)\n",
    "\n",
    "    train_preds = maml_model.predict(train_images)\n",
    "    train_acc = np.mean(np.argmax(train_preds, axis=1) == train_labels)\n",
    "    train_acc_history.append(train_acc)\n",
    "\n",
    "    print(f\"Epoch {epoch + 1}: Mean Query Loss: {mean_query_loss:.4f}\")\n",
    "\n",
    "    val_preds = maml_model.predict(val_images)\n",
    "    val_loss = tf.reduce_mean(tf.keras.losses.sparse_categorical_crossentropy(val_labels, val_preds)).numpy()\n",
    "    val_acc = np.mean(np.argmax(val_preds, axis=1) == val_labels)\n",
    "\n",
    "    val_loss_history.append(val_loss)\n",
    "    val_acc_history.append(val_acc)\n",
    "\n",
    "    print(f\"Validation Loss: {val_loss:.4f}, Accuracy: {val_acc * 100:.2f}%\")\n",
    "\n",
    "    if val_loss < best_val_loss:\n",
    "        best_val_loss = val_loss\n",
    "        wait = 0\n",
    "        maml_model.save(os.path.join(save_dir, 'best_model.h5'))\n",
    "    else:\n",
    "        wait += 1\n",
    "        if wait >= patience:\n",
    "            print(f\"Early stopping at epoch {epoch + 1}\")\n",
    "            break\n",
    "\n",
    "# Final test\n",
    "test_preds = maml_model.predict(test_images)\n",
    "test_loss = tf.reduce_mean(tf.keras.losses.sparse_categorical_crossentropy(test_labels, test_preds)).numpy()\n",
    "test_acc = np.mean(np.argmax(test_preds, axis=1) == test_labels)\n",
    "print(f'Final Test Loss: {test_loss:.4f}, Accuracy: {test_acc * 100:.2f}%')\n",
    "\n",
    "# Plotting the training and validation history\n",
    "plt.figure(figsize=(12, 4))\n",
    "plt.subplot(1, 2, 1)\n",
    "plt.plot(train_loss_history, label='Train Loss', color='blue')\n",
    "plt.plot(val_loss_history, label='Validation Loss', color='orange')\n",
    "plt.title('Training and Validation Loss')\n",
    "plt.xlabel('Epochs')\n",
    "plt.ylabel('Loss')\n",
    "plt.legend()\n",
    "\n",
    "plt.subplot(1, 2, 2)\n",
    "plt.plot(train_acc_history, label='Train Accuracy', color='blue')\n",
    "plt.plot(val_acc_history, label='Validation Accuracy', color='orange')\n",
    "plt.title('Training and Validation Accuracy')\n",
    "plt.xlabel('Epochs')\n",
    "plt.ylabel('Accuracy')\n",
    "plt.legend()\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6ff9d01d-a96b-4a1d-81e0-d5995bb739b4",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.metrics import classification_report\n",
    "test_preds = maml_model.predict(test_images)\n",
    "test_loss = tf.reduce_mean(tf.keras.losses.sparse_categorical_crossentropy(test_labels, test_preds)).numpy()\n",
    "test_acc = np.mean(np.argmax(test_preds, axis=1) == test_labels)\n",
    "print(f'Final Test Loss: {test_loss:.4f}, Accuracy: {test_acc * 100:.2f}%')\n",
    "test_preds = maml_model.predict(test_images)\n",
    "test_preds_labels = np.argmax(test_preds, axis=1)\n",
    "\n",
    "test_loss = tf.reduce_mean(tf.keras.losses.sparse_categorical_crossentropy(test_labels, test_preds)).numpy()\n",
    "test_acc = np.mean(test_preds_labels == test_labels)\n",
    "\n",
    "print(f'Final Test Loss: {test_loss:.4f}, Accuracy: {test_acc * 100:.2f}%')\n",
    "\n",
    "# Classification report\n",
    "print(\"\\nClassification Report:\")\n",
    "print(classification_report(test_labels, test_preds_labels, target_names=['LS', 'SF', 'tmp']))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6f62684b-bbdc-45a7-b330-36019a5bfccf",
   "metadata": {},
   "outputs": [],
   "source": [
    "#confusion matrix\n",
    "from sklearn.metrics import classification_report, confusion_matrix, ConfusionMatrixDisplay\n",
    "\n",
    "test_preds = maml_model.predict(test_images)\n",
    "test_pred_labels = np.argmax(test_preds, axis=1)\n",
    "\n",
    "test_loss = tf.reduce_mean(tf.keras.losses.sparse_categorical_crossentropy(test_labels, test_preds)).numpy()\n",
    "test_acc = np.mean(test_pred_labels == test_labels)\n",
    "print(f'Final Test Loss: {test_loss:.4f}, Accuracy: {test_acc * 100:.2f}%')\n",
    "\n",
    "# Classification report\n",
    "from sklearn.metrics import classification_report\n",
    "print(\"\\nClassification Report:\")\n",
    "print(classification_report(test_labels, test_pred_labels, target_names=['LS', 'SF', 'tmp']))\n",
    "\n",
    "# Confusion matrix\n",
    "cm = confusion_matrix(test_labels, test_pred_labels)\n",
    "disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=['LS', 'SF', 'tmp'])\n",
    "disp.plot(cmap='Blues', values_format='d')\n",
    "plt.title(\"Confusion Matrix\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d6d61f41-a039-4342-b4c3-7c259a7d088e",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
